rapid application development using ruby on rails
DESCRIPTION
Rapid Application Development using Ruby on RailsTRANSCRIPT
Rapid Application Development using Ruby on Rails
Presented By: Hisham Malik http://www.arkhitech.com
1
Day 1 - Overview/Quickstart
• Overview of RoR• Setting up Environment• RoR Quick-start Demo• Ruby Language Fundamentals• Rails MVC• Live Demo• Under the Hood
2
Day 2 - Details
• DB Migrations• MVC – ActiveRecord, ActionController, ActionView• Form Helpers• Routing
3
Day 3 - Optimizations
• Testing • Ruby Language – More Details.• Active Records - More Details.• Live Demo Contd.• AJAX – Unobtrusive Javascripting (UJS)• Security
4
Day 4 – Supporting Gems• Cookies/Session
• Remember me Example• Capistrano • Caching• Testing Contd. • Database Optimizations• Amazon S3 Storage – AWS::S3• Attachments – Paperclip• Master/Slave Replication – Octopus• Grids – WiceGrid• Full-text Searching – Thinking Sphinx• Message Queues/Delayed Jobs • AJAX Contd.• Security Contd.• Testing Contd.• Quick Overview of Active Resource
5
Day 1
6
Ruby on Rails
• Ruby on Rails is an open-source web framework that’s optimized for programmer happiness and sustainable productivity.
• Lets you write beautiful code by favoring Convention over Configuration (CoC) and rapid development principle of Don't Repeat Yourself (DRY).
7
Ruby on Rails
• Ruby• less and more readable code, • shorter development times, • simple but powerful, • no compilation cycle
• Convention over configuration• almost no config files, • predefined directory structure, • naming conventions
• less code, • easier maintenance 8
Ruby on Rails
• Best practices: • MVC, • DRY, • Testing
• Almost everything in Rails is Ruby code (SQL and JavaScript are abstracted)
• Unobtrusive AJAX support. • Web services with REST.• Good community, tools, and documentation
9
Environment Check!
• Ruby - 2.0.0• ruby -v
• Rails - 4.0.0• rails -v
10
Installing Rails
• Installing Rails• gem install rails #usually run as root user
• If you’re using windows, it is suggested that you install a Linux virtual machine and use that for Rails development.
• While Ruby and Rails themselves install easily, the supporting ecosystem often assumes you are able to build C-based rubygems and work in a command window.
11
Getting Up and Running Quickly - Demo
12
Creating Sample Application
• rails new sample• You can see all of the switches that the Rails application builder
accepts by running rails -h.
13
Generating User Resource
• The argument of the scaffold command is the singular version of the resource name (in this case, User), together with optional parameters for the data model’s attributes
14
Generated User Pages/URL
15
Ruby“I always thought Smalltalk would beat Java. I just didn’t know it would be called ‘Ruby’ when it did”- Kent Beck
16
Ruby Principles
• Focusing on the human• Dynamic Programming• Principle of Least Surprise• Principle of Succinctness• Lightweight Language• Choosing good names• Embedding hidden messages
17
Focusing on the human
• Lets you focus on problems• Convention over Configuration• Lets you stay at a higher level of abstraction• Extensive class libraries• Provides garbage collection
18
Principle of Least Surprise
• diff = ary1 – ary2• union = ary1 + ary2
19
Principle of Succinctness
• Developers shouldn't get stressed by time that is spent.• Quicker we program, the more we program• Faster we finish, better programmer we can be.• Writing less code, introducing less bugs, and hence be more
productive!
20
The Ruby Language
• Generic, interpreted, reflective, with garbage collection• Optimized for people rather than computers• More powerful than Perl, more object oriented than Python• Everything is an object. There are no primitive types.• Strong dynamic typing
21
Assignment
• a, b = b, a # swapping values• a = 1; b = 1• a = b = 1• a += 1 # a = a + 1• a, b = [1, 2]• a = b || c• a ||= b
22
Boolean Expressions
• All objects evaluate to true except false and nil• false and true are the only instances of FalseClass and
TrueClass• Boolean expressions return the last evaluated object• The "word" versions have lower precedence than the "symbol"
versions. In fact, they have even lower precedence than assignment.
• a and b or c <=> (a and b) or c• a = b and c <=> (a = b) and c• a = b && c <=> a = (b && c)• puts a if a = b # Using assignments in boolean expressions• a = true; b = false; a and b and c() # => c() is never inoked
23
Class Definition and Instantiation• class Person • def initialize(name) • @name = name • end • def say_hi • puts "#{@name} says hi" • end • end • andreas = Person.new("Andreas") • andreas.say_hi
24
Class Inheritance
• class Programmer < Person • def initialize(name, favorite_ide) • super(name) • @favorite_ide = favorite_ide • end • # We are overriding say_hi in Person • def say_hi • super • puts "Favorite IDE is #{@favorite_ide}" • end • end • peter = Programmer.new("Peter", "TextMate") • peter.say_hi
25
Naming Conventions
• class MyClass• method_name, dangerous_method!,• question_method?, setter_method=• MY_CONSTANT = 3.14• local_variable = 3.14• @instance_variable• @@class_variable• $global_variable
26
Methods
• When invoking a method argument parenthesis are optional• Methods always have a receiver. The implicit receiver is self.• Methods are identified by their name only. No overloading on
Argument 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 = {})
27
Instance Methods/Getters and Setters• class Person • def initialize(name) • self.name = name • end • def name • @name • end • def name=(name) • @name = name • end • end • person = Person.new("Andreas") • puts person.name • person.name = "David" • puts person.name
28
attr_accessor
• class Person • attr_accessor :name • def initialize(name) • self.name = name • end • end
• person = Person.new("Andreas") • puts person.name • person.name = "David" • puts person.name 29
attr_reader
• class Person • attr_reader :name • def initialize(name) • @name = name • end • end • person = Person.new("Andreas") • puts person.name
30
Variable/Method Ambiguity Gotcha• class Person • attr_accessor :paid • def initialize • @paid = false • end • def make_payment • puts "making payment..." • paid = true • end • end • person = Person.new • person.make_payment • puts "paid=#{person.paid}"
31
Class Methods
• class Person• def self.class_method• puts “class method invoked”• end• class << self• def class_method2; puts “class_method2”; end• def class_method3; puts “class_method3”; end• end• end• class << User• def class_method4; puts “class_method4”; end• end
32
Class Instance Methods
• andreas = Person.new(“Andreas”)• def andreas.andreas_says_hi• “Andreas says hi”• end• andreas.andreas_says_hi
33
Idiom: Assignment with Boolean Operators• # Overly verbose:• user_id = nil• if comments• if comments.first• if comments.first.user• user_id = comments.first.user.id• end• end• end• # Idiomatic:• user_id = comments && comments.first &&• comments.first.user && comments.first.user.id
34
Module
• # Mixins - instead of multiple inheritance• module FullName• def full_name• "#{first_name} #{last_name}"• end• end• class Person• include FullName• end• Person.new("Peter", "Marklund").full_name
35
Modules as Namespaces
• # Namespaces - to avoid name collissions• module MyApp• class Person• attr_accessor :name• def initialize(name)• self.name = name• end• end• end• MyApp::Person.new("Peter Marklund")
36
Modules vs Classes
• Modules model characteristics or properties of entities or things. • Modules can’t be instantiated!• Module names tend to be adjectives (Comparable,• Enumerable etc.). • A class can mix in several modules.
• Classes model entities or things. • Class names tend to be nouns. • A class can only have one super class• (Enumeration, Item etc.).
37
String Class
• “ruby”.upcase + “ on “ + “rails”.capitalize• “Current time is: #{Time.now}\n second line”• “I“ << “like” << “Ruby”• <<-END• Here we are creating multi-line document• document created at #{Time.now}• END• "Ruby is Great"[8,5] # => “Great”• "Ruby is Great"[0..3] == “Ruby” # => true• sprintf(“value of %s is %.2f”,”PI”,3.1416)
38
Array Class• a = [“Ruby”, 99, 3.14] #Elements of array need not be of same type• a[1] == 99 • a << “Rails” #append element to the end of the array• [‘C’, ‘Java’, ‘Ruby’] == %w{C Java Ruby} #same• [1, 2, 3].map { |x| x**2 }.join(“, “) • [1, 2, 3].select { |x| x % 2 == 0 } #• [1, 2, 3].reject { |x| x < 3 }• [1, 2, 3].inject { |sum, i| sum + i }• [1, [2, 3]].flatten! # => [1, 2, 3]• ['C', 'Java', 'Ruby'].include?(language)• fruits = [‘apple’, ‘banana’]• fruits += [‘apple’] • fruits |= [‘apple’]
39
Symbol Class• A Ruby symbol looks like a colon followed by characters. (:mysymbol)• A Ruby symbol is a thing that has both a number (integer) and a string.• The value of a Ruby symbol's string part is the name of the symbol, minus the
leading colon.• A Ruby symbol cannot be changed at runtime. • Neither its string representation nor its integer representation can be
changed at runtime.• Like most other things in Ruby, a symbol is an object.• When designing a program, you can usually use a string instead of a symbol.• Except when you must guarantee that the string isn't modified.• Symbol objects do not have the rich set of instance methods that String
objects do.• After the first usage of :mysymbol all further useages of :mysymbol take no
further memory -- they're all the same object.• Ruby symbols save memory over large numbers of identical literal strings.• Ruby symbols enhance runtime speed to at least some degree.
40
Hash Class
• h = {lang: ‘Ruby’, framework: ‘Rails’}• h[:lang] == ‘Ruby’• h[:perl] == nil• ENV = {‘USER’ => ‘peter’, ‘SHELL’ => ‘/bin/bash'}• ENV.each {|k, v| puts “#{k}=#{v}” }
41
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]
42
Easier Conditional Statements• puts “Good Morning” if Time.now.hour < 12• puts “Good Night” unless Time.now.hour < 20
43
Iterators: while, until,and for. Keywords: breakand next• while count < 100• puts count• count += 1• end• # Statement modifier version of while• payment.make_request while (payment.failure? and payment.tries < 3)• for user in @users• next if user.admin?• if user.paid?• puts user• break• end• end• until count > 5• puts count• count += 1• end• # Statement modifier version of until• puts(count += 1) until count > 5
44
Case Statement• case x• when 0• when 1, 2..5• puts "Second branch"• when 6..10• puts "Third branch"• when *[11, 12]• puts “Fourth branch”• when String: puts “Fifth branch”• when /\d+\.\d+/• puts “Sixth branch”• when x.downcase == “peter”• puts “Seventh branch”• else• puts "Eight branch"• end
• end
• end
45
Regular Expressions
• puts 'matches' if ‘Ruby’ =~ /^(ruby|python)$/i• “Go\nRuby” =~ /Go\s+(\w+)/m; $1 == 'Ruby'• pattern = “.”; Regexp.new(Regexp.escape(pattern))• 'I like Ruby'[/(like)/i, 1] == 'like'• 'I like Ruby'[/(like).*(ruby)/i,2] == 'Ruby'• 'I like Ruby'.gsub(/ruby/i) { |lang| lang.upcase }• line = 'I Go Ruby'• m, who, verb, what = *line.match(/^(\w+)\s+(\w+)\s+(\w+)$/)• \s, \d, [0-9], \w - space, digit, and word character classes• ?, *, +, {m, n}, {m,}, {m} - repetition
46
Exceptions• begin• raise(ArgumentError, “No file_name provided”) if !file_name• content = load_blog_data(file_name)• raise “Content is nil” if !content• rescue BlogDataNotFound• STDERR.puts "File #{file_name} not found"• rescue BlogDataConnectError• @connect_tries ||= 1• @connect_tries += 1• retry if @connect_tries < 3• STDERR.puts "Invalid blog data in #{file_name}"• rescue Exception => exc• STDERR.puts "Error loading #{file_name}: #{exc.message}"• raise• end
47
Rails MVC
48
Models
• A model represents the information (data) of the application and the rules to manipulate that data.
• In Rails, models are primarily used for managing the rules of interaction with a corresponding database table.
• In most cases, one table in your database will correspond to one model in your application.
• The bulk of your application’s business logic will be concentrated in the models.
49
Views
• Views represent the user interface of your application. • In Rails, views are often HTML files with embedded Ruby code
that perform tasks related solely to the presentation of the data.
• Views handle the job of providing data to the web browser or other tool that is used to make requests from your application.
50
Controllers
• Controllers provide the “glue” between models and views. • In Rails, controllers are responsible for processing the incoming
requests from the web browser, interrogating the models for data, and passing that data on to the views for presentation.
51
Rails - MVC
52
MVC Request Cycle in Detail
The browser issues a request for the /users URL.Rails routes /users to the index action in the Users controller.The index action asks the User model to retrieve all users (User.all).The User model pulls all the users from the database.The User model returns the list of users to the controller.The controller captures the users in the @users variable, which is passed to the indexview.The view uses embedded Ruby to render the page as HTML.The controller passes the HTML back to the browser.
53
Creating Blog Project
54
Creating Blog Application
• rails new blog• You can see all of the switches that the Rails application builder
accepts by running rails -h.• Rails applications manage gem dependencies with Bundler.• bundle install• #create stubs in bin/ folder• bundle install --binstubs
55
Configuring Database• The database to use is specified in a configuration file,
config/database.yml. By default SQLite3 is supported which is a serverless database.
• The file contains sections for three different environments in which Rails can run by default:
• The development environment is used on your development computer as you interact manually with the application
• The test environment is used to run automated tests• The production environment is used when you deploy your
application for the world to use.• Creating the Database
• bundle exec rake db:create56
Using MySQL (optional)
• Add Mysql in Gemfile and run bundle install• gem "mysql", "~> 2.9.1"• Update Contents of database.yml file:• development:• adapter: mysql• encoding: utf8• reconnect: false• encoding: utf8• database: blog_development• pool: 5• username: root• password:• socket: /tmp/mysql.sock
57
Hello Rails
• rails server
58
Rake
Rake is Ruby make, a make-like language written in Ruby. Rails uses Rake extensively, especially for the innumerable little administrative tasks necessary when developing database-backed web applications. Rake lets you define a dependency tree of tasks to be executed.Rake tasks are loaded from the file RakefileYou can put your own tasks under lib/tasksbundle exec rake -Tbundle exec rake -T db #See a list of database tasks
59
Useful Rake Tasks• about
• rake about gives information about version numbers for Ruby, RubyGems, Rails, the Rails subcomponents, your application's folder, the current Rails environment name, your app's database adapter, and schema version. It is useful when you need to ask for help, check if a security patch might affect you, or when you need some stats for an existing Rails installation.
• assets• You can precompile the assets in app/assets using rake assets:precompile and
remove those compiled assets using ‘rake assets:clean’.• db
• The most common tasks of the db: Rake namespace are ‘migrate’ and ‘create’, and it will pay off to try out all of the migration rake tasks (‘up’, ‘down’, ‘redo’, ‘reset’). ‘rake db:version’ is useful when troubleshooting, telling you the current version of the database.
• doc• If you want to strip out or rebuild any of the Rails documentation, the
doc:namespace has the tools. Stripping documentation is mainly useful for slimming your codebase, like if you’re writing a Rails application for an embedded platform.
60
Useful Rake Tasks
• notes• These tasks will search through your code for commented
lines beginning with “FIXME”, “OPTIMIZE”, or “TODO”.• stats
• Gives summary statistics about your code• routes
• Lists all your defined routes• secret
• Will give you a pseudo-random key to use for your session secret.
• time:zones:all• Lists all the timezones Rails knows about.
61
Live Demo
62
Setting up Home Page
• Create Home controller with index action• rails generate controller home index
• Edit app/views/home/index.html.erb• <h1>Hello Rails Workshop Participants!</h1>
• Remove public/index as static files have precedence over dynamic content generated.• rm public/index.html
• Setup Rails to point to your home.• Open the file config/routes.rb and uncomment/add the
following line:• root :to => "home#index"
63
Scaffolding
• Rails scaffolding is a quick way to generate some of the major pieces of an application.
• If you want to create the models, views, and controllers for a new resource in a single operation, scaffolding is the tool for the job.
• rails generate scaffold Post name:string title:string content:text
64
Creating Post Resource
• rails generate scaffold Post name:string title:string content:text• bundle exec rake db:migrate
65
Linking Posts to Home Page
• Open app/views/home/index.html.erb and modify it as follows:
• <h1>Hello, Rails Workshop Participants!</h1> <%= link_to "My Blog", posts_path %>
• The link_to method is one of Rails’ built-in view helpers. It creates a hyperlink based on text to display and where to go – in this case, to the path for posts.
66
Under The Hood
67
User Resource Limitations
• No data validations. Our User model accepts data such as blank names and invalid email addresses without complaint.
• No authentication. We have no notion signing in or out, and no way to prevent any user from performing any operation.
• No layout. There is no consistent site styling or navigation.• No real understanding.
68
Posts Model
• #app/models/post.rb• class Post < ActiveRecord::Base• end
69
Post Model
• Adding some basic validation• class Post < ActiveRecord::Base• validates :name, presence: true• validates :title, presence: true, length: { minimum: 5 }• end• More on validation later!
70
Post Controllerapp/controller/posts_controller.rb
71
Index Method
• Result stored in an instance variable• @posts = Post.all
• The respond_to block handles both HTML and XML calls to this action• The HTML format looks for a view in app/views/posts/ with
a name that corresponds to the action name, e.g, app/views/posts/index.html.erb
72
View: Index
• app/views/posts/index.html.erb• View iterates over the contents of the @posts array to
display content and links.• link_to builds a hyperlink to a particular destination• edit_post_path and new_post_path are helpers that Rails
provides as part of RESTful routing. • A application specific layout is used for all the controllers and
can be found in app/views/layouts/application.html.erb
73
New Method
• Result stored in an instance variable• @posts = Post.all
• The respond_to block handles both HTML and XML calls to this action• The HTML format looks for a view in app/views/posts/ with
a name that corresponds to the action name, e.g, app/views/posts/new.html.erb
74
View: New
• app/views/posts/new.html.erb• View renders a form residing in
app/views/posts/_form.html.erb• <%= render 'form' %>
• app/views/posts/_form.html.erb• The form_for block is used to create an HTML form• The form_for block is also smart enough to work out if you
are doing a New Post or an Edit Post action, and will set the form action tags and submit button names appropriately in the HTML output.
75
Comments can be given for Post?
76
Generating a Referencing Model• To add a connection between your tables we want to add
references in our model. • References are comparable to foreign keys• rails generate model Comment commenter:string body:text
post:references • Files generated
• app/models/comment.rb – The model• db/migrate/20100207235629_create_comments.rb – The
migration• test/unit/comment_test.rb and • test/fixtures/comments.yml – The test harness.
77
Linking Models
• Edit app/models/post.rb to add the other side of the association• has_many :comments
• Add route for comments in config/routes.rb• resources :posts do• resources :comments• end
• More on associations and routes later!
78
Comments Controller?
79
Generating Comments Controller• rails generate controller Comments• This creates six files and one empty directory:
• app/controllers/comments_controller.rb – The controller• app/helpers/comments_helper.rb – A view helper file• test/controllers/comments_controller_test.rb – The functional
tests for the controller• test/helpers/comments_helper_test.rb – The unit tests for the
helper• app/views/comments/ – Views of the controller are stored
here• app/assets/javascripts/welcome.js.coffee - CoffeeScript for the
controller• app/assets/stylesheets/welcome.css.scss - Cascading style
sheet for the controller
80
Add Comments to Post
• Comments should be added where the post is shown.
81
Add Comments to Post• Append comments form to Post show template (app/views/posts/show.html.erb)• <h2>Add a comment:</h2>• <%= form_for([@post, @post.comments.build]) do |f| %>• <div class="field">• <%= f.label :commenter %><br />• <%= f.text_field :commenter %>• </div>• <div class="field">• <%= f.label :body %><br />• <%= f.text_area :body %>• </div>• <div class="actions">• <%= f.submit %>• </div>• <% end %>• • <%= link_to 'Edit Post', edit_post_path(@post) %> |• <%= link_to 'Back to Posts', posts_path %> |
82
Handle Comments to Post
• Add create method in CommentsController• class CommentsController < ApplicationController• def create• @post = Post.find(params[:post_id])• @comment = @post.comments.create(params[:comment])• redirect_to post_path(@post)• end• end
83
Show Comments
• Add section to show comments for posts to Post show template (app/views/posts/show.html.erb)
• <h2>Comments</h2>• <% @post.comments.each do |comment| %>• <p>• <b>Commenter:</b>• <%= comment.commenter %>• </p>• <p>• <b>Comment:</b>• <%= comment.body %>• </p>• <% end %>
84
Day 2
85
Rails Console• command-line tool that lets you execute Ruby code in the context of your
application.• Allows you to work with your models/functions• >> p = Post.new(:content => "A new post")• => #<Post id: nil, name: nil, title: nil,• content: "A new post", created_at: nil,• updated_at: nil>• >> p.save• => false• >> p.errors• => #<OrderedHash { :title=>["can't be blank",• "is too short (minimum is 5 characters)"],• :name=>["can't be blank"] }>• >> helper.text_field_tag(:person,:name)• => "<input id=\"person\" name=\"person\" type=\"text\" value=\"name\" />"
86
Migrations
A way to evolve your database schema over timeMigrations use a database independent Ruby APIMigration classes extend ActiveRecord::Migration and have an up and a down methodMigrations are stored in files in db/migrate, one for each migration class. Running migrations:bundle exec rake db:migrate
87
ActiveRecord::Migration
• class CreatePosts < ActiveRecord::Migration• def self.up• create_table :posts do |t|• t.string :name• t.string :title• t.text :content• t.timestamps• end• end • def self.down• drop_table :posts• end• end
88
Migration Methods• create_table(name, options)
• Creates a table called name and makes the table object available to a block that can then add columns to it, following the same format as add_column. See example above. The options hash is for fragments like “DEFAULT CHARSET=UTF-8” that are appended to the create table definition.
• drop_table(name) • Drops the table called name.
• rename_table(old_name, new_name)• Renames the table called old_name to new_name.
• add_column(table_name, column_name, type, options): • Adds a new column to the table called table_name named column_name
specified to be one of the following types: :primary_key, :string, :text, :integer, :float, :decimal, :datetime, :timestamp, :time, :date, :binary, :boolean. A default value can be specified by passing an options hash like { :default => 11 }. Other options include :limit and :null (e.g. { :limit => 50, :null => false })
89
Migration Methods• rename_column(table_name, column_name, new_column_name)
• Renames a column but keeps the type and content.• change_column(table_name, column_name, type, options)
• Changes the column to a different type using the same parameters as add_column.
• remove_column(table_name, column_name)• Removes the column named column_name from the table called
table_name.• add_index(table_name, column_names, options)
• Adds a new index with the name of the column. Other options include :name and :unique (e.g. { :name => "users_name_index", :unique => true }).
• remove_index(table_name, index_name)• Removes the index specified by index_name.
90
Generating Migrations w/Model• rails generate model Product name:string description:text
• class CreateProducts < ActiveRecord::Migration• def self.up• create_table :products do |t|• t.string :name• t.text :description• • t.timestamps• end• end• def self.down• drop_table :products• end• end
91
Generating Migration to Add Columns• rails generate migration
• Add<Desc>To<Model> - Add given columns to table specified by Model• rails generate migration AddDetailsToProducts part_number:string
price:decimal• class AddDetailsToProducts < ActiveRecord::Migration• def self.up• add_column :products, :part_number, :string• add_column :products, :price, :decimal• end• def self.down• remove_column :products, :price• remove_column :products, :part_number• end• end
92
Generating Migrations to Remove Columns• rails generate migration
• Remove<Desc>To<Model> - Remove given columns from table specified by Model
• rails generate migration RemovePartNumberFromProducts part_number:string
• class RemovePartNumberFromProducts < ActiveRecord::Migration• def self.up• remove_column :products, :part_number• end• def self.down• add_column :products, :part_number, :string• end• end 93
Creating Table• create_table :products do |t|• t.string :name• end• The types supported by Active Record
are :primary_key, :string, :text, :integer, :float, :decimal, :datetime, :timestamp, :time, :date, :binary, :boolean.
• You may use a type not in this list as long as it is supported by your database (for example, “polygon” in MySQL), but this will not be database agnostic and should usually be avoided.
• create_table :products do |t|• t.column :name, 'polygon', :null => false• end• Available options are (none of these exists by default):
• :limit - Requests a maximum column length. This is number of characters for :string and :text columns and number of bytes for :binary and :integer columns.
• :default - The column’s default value. Use nil for NULL.• :null - Allows or disallows NULL values in the column. This option could have been
named :null_allowed.• :precision - Specifies the precision for a :decimal column.• :scale - Specifies the scale for a :decimal column.
94
Updating Tables• change_table :products do |t|• t.remove :description, :name• t.string :part_number• t.index :part_number• t.rename :upccode, :upc_code• end
• remove_column :products, :description• remove_column :products, :name• add_column :products, :part_number, :string• add_index :products, :part_number• rename_column :products, :upccode, :upc_code
95
functionally equivalent
functionally equivalent
Managing Migrations• Running the db:migrate also invokes the db:schema:dump task, which will
update your db/schema.rb file to match the structure of your database.• If you specify a target version, Active Record will run the required migrations
(up or down) until it has reached the specified version• rake db:migrate VERSION=20080906120000
• To rollback migrations:• rake db:rollback #run the down method from latest migration.• rake db:rollback STEP=3 #run the down method from the last 3 migrations.
• To rollback and redo migrations:• rake db:migrate:redo #run the down method from latest migration.• rake db:migrate:redo STEP=3 #run the down method from the last 3
migrations.• Load database from current schema:
• db:setup #• db:reset #drops existing database first
96
Handling Model Changes• Information about model columns is cached• You can force Active Record to re-read the column information with
the reset_column_information method• class AddPartNumberToProducts < ActiveRecord::Migration• def self.up• add_column :product, :part_number, :string• Product.reset_column_information• ...• end• def self.down• ...• end• end
97
Seed Data
• Adding seed data into migrations isn’t the best way to add initial data.
• Put all initial data in db/seeds.rb• [‘Lahore’, ‘Karachi’, ‘Islamabad’].each do |city|
• City.find_or_create_by_name(city)• end• rake db:seed
98
Boolean Attributes
Everything except nil and false is true in RubyHowever, 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 number 0, one of the strings ‘0’, ‘f ’, ‘false’, or ‘’, or the constant false
99
Active Record
100
Fundamentals
One database table maps to one Ruby classRuby classes live under app/models and extend ActiveRecord::BaseTable names are plural and class names are singularDatabase columns map to attributes, i.e. get and set methods, in the model classAll tables have an integer primary key called idDatabase tables are created with migrations
101
Methods
whereselectgrouporderlimitoffsetjoinsincludeslockreadonlyfromhaving
102
Retrieving Single Object
• Using Primary Key• client = Client.find(10)• SELECT * FROM clients WHERE (clients.id = 10)
• first• client = Client.first• SELECT * FROM clients LIMIT 1
• last• client = Client.last• SELECT * FROM clients ORDER BY clients.id DESC LIMIT 1
103
Retrieving Multiple Objects• Using Multiple Primary Keys
• client = Client.find(1, 10) # Or even Client.find([1, 10])• SELECT * FROM clients WHERE (clients.id IN (1,10))
• find_each• find_each fetches rows in batches of 1000 and yields them one by
one• User.find_each(:batch_size => 5000, :start => 2000) do |user|• NewsLetter.weekly_deliver(user)• end
• find_in_batches• Analogous to find_each, but it yields arrays of models instead• Invoice.find_in_batches() do |invoices|• export.add_invoices(invoices)• end
104
Conditions
• Pure String Conditions• Client.where("orders_count = '2'")
• Array Conditions• Client.where("orders_count = ? AND locked = ?",
params[:orders], false)• Client.where("created_at >= :start_date AND created_at
<= :end_date", {:start_date => params[:start_date], :end_date => params[:end_date]})
105
Action Controller
• Controllers are Ruby classes that live under app/controllers• Controller classes extend ActionController::Base• An action is a public method and/or a corresponding view
template
106
The Rails Configuration Object# Defined in railties/lib/initializer.rb Rails.root Rails.env Rails.version Rails.configuration.load_paths Rails.configuration.gems Rails.configuration.plugins Rails.configuration.time_zone Rails.configuration.i18n
107
Controller Environment
• 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”)
108
Rendering Response
• A response is rendered with the render command• An action can only render a response once• Rails invokes render automatically if you don’t• Redirects are made with the redirect_to command• You need to make sure you return from an action after an
invocation of render or redirect_to
109
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_file(“/files/some_file.pdf ”)
110
Redirect
• Tells the browser to send a new request for a different URL redirect_to :back
• redirect_to(“/help/order_entry.html”)• redirect_to :controller => ‘blog’, :action => ‘list’• redirect_to photos_path, :status => 301
111
Flash
• The flash is a way to set a text message to the user in one request and then display it in the next (typically after a redirect)
• The flash is stored in the session• flash[:notice], flash[:error]• flash.now[:notice] = “Welcome” unless flash[:notice]• flash.keep(:notice)
112
Flash.now• There’s a subtle difference between flash and flash.now. • The flash variable is designed to be used before a redirect, and it persists
on the resulting page for one request—that is, it appears once, and disappears when you click on another link.
• If we don’t redirect, and instead simply render a page, the flash message persists for two requests: • it appears on the rendered page but is still waiting for a “redirect” (i.e.,
a second request), and • thus appears again if you click a link.
• To avoid this weird behavior, when rendering rather than redirecting we use flash.now instead of flash.
• The flash.now object is specifically designed for displaying flash messages on rendered pages.
• If you ever find yourself wondering why a flash message is showing up where you don’t expect it, chances are good that you need to replace flash with flash.now.
113
Displaying Flash
• #app/views/layouts/application.html.erb• <!DOCTYPE html>• <html>• ...• <%= render 'layouts/header' %>• <section class="round">• <% flash.each do |key, value| %>• <div class="flash <%= key %>"><%= value %></div>• <% end %>• <%= yield %>• </section>• ...• </html>
114
ActionView Basics
• The controller decides which template and/or partial and layout to use in the response
• Templates use helper methods to generate links, forms, and JavaScript, and to format text.
• A response is rendered with the render command• An action can only render a response once• Rails invokes render automatically if you don’t• Redirects are made with the redirect_to command• You need to make sure you return from an action after an
invocation of render or redirect_to
115
Templates
• Each action in the controller can have an associated template. • Templates belonging to a certain controller are under
app/view/controller_name. For example, templates for GiftController would be under app/views/gift
• Templates shared across controllers are put under app/views/shared. You can render them with render :template => ‘shared/my_template’
116
Template Environment
• Templates have access to the controller objects flash, headers, logger, params, request, response, and session.
• Instance variables (i.e. @variable) in the controller are available in templates
• The current controller is available as the attribute controller.
117
Partials
• Partials are templates that render a part of a page, such as a header or footer, or a menu, or a listing of articles
• Partials help promote reuse of page elements• Partials work just like page templates (views) and run in the
same environment. They also live in the same directory as page templates.
• The filenames of partials always start with an underscore.
118
Passing Variables toPartials• Controller instance variables are available in partials• If you pass :object => @an_article to the render command then
that variable will be available in a local variable in the partial with the same name as the partial.
• If there is an instance variable with the same name as the partial then it will be available as a local variable in the partial with the same name, i.e. @article = Article.find(1); render :partial =>‘article’.
• You can pass any objects into local variables in the partial with the :locals argument: render :partial => ‘article’, :locals => { :author => @author, :options => @options }
119
Partials andCollections• <% for article in @articles %>• <%= render :partial => ‘article’, :object => article %>• <% end %>• Can be written more concisely with the :collections argument:• <%= render :partial => ‘article’, :collection => @articles %>
120
Layouts
• Layouts are templates under app/views/layouts that contain common page elements around pages such as headers, footers, menus etc.
• The layout template contains the invocation <%=yield %> which will render the action output.
121
Layout Selection
• To find the current layout, Rails • first looks for a file in app/views/layouts with the same base
name as the controller. For example, rendering actions from the PhotosController class will use app/views/layouts/photos.html.erb (or app/views/layouts/photos.builder).
• If there is no such controller-specific layout, Rails will use app/views/layouts/application.html.erb or app/views/layouts/application.builder.
• If there is no .erb layout, Rails will use a .builder layout if one exists. Rails also provides several ways to more precisely assign specific layouts to individual controllers and actions.
122
Specifying Layout
• Layout can be specified on a per-controller basis• class ProductsController < ApplicationController• layout "inventory"• #...• end• Conditional Layouts• class ProductsController < ApplicationController• layout "product", :except => [:index, :rss]• end
123
Selecting Layout at Runtime
• class ProductsController < ApplicationController• layout :products_layout• def show• @product = Product.find(params[:id])• end• private• def products_layout• @current_user.special? ? "special" : "products"• end • end
124
Selecting Layout at Runtime
• class BlogController < ActionController::Base• layout :determine_layout• private• def determine_layout• user.admin? ? “admin” : “standard”• end• end
125
Yield/Content_for
• Layout:• <html>• <head>• <%= yield :head %>• </head>• <body>• <%= yield %>• </body>• </html>
126
Yield/Content_for/Provide
• <%= content_for :head do %>• <title>A simple page</title>• <% end %>• <%= provide :head %> • <p>Hello, Rails!</p>
127
Best Practice
• Don’t put SQL and too much code in your controllers/views - it’s a code smell, and maybe the most common design mistake Rails developers make. Actions should be 3-4 lines that script business objects.
• Goal is fat models and skinny controllers.• Always access data via the logged in user object (i.e.
current_user.visits.recent).
128
Helpers
• Helpers are Ruby modules with methods that are available in your templates.
• Helpers can avoid duplication and minimize the amount of code in your templates.
• By default each controller has a corresponding helper file at app/helpers/controller_name_helper.rb
• To test ActionView helpers in rails console, use• include ActionView::Helpers
129
AssetTagHelper
• Provide methods for generating HTML that links views to feeds, JavaScript, stylesheets, images, videos and audios.
• http://api.rubyonrails.org/classes/ActionView/Helpers/AssetTagHelper.html• auto_discovery_link_tag
• <link rel="alternate" type="application/rss+xml" title="RSS" href="http://www.currenthost.com/controller/action" />
• javascript_include_tag "xmlhr"• <script type="text/javascript"
src="/javascripts/xmlhr.js"></script>• stylesheet_link_tag("application")
• <link href="/stylesheets/application.css?1232285206" media="screen" rel="stylesheet" type="text/css" />
130
AutoDiscovery/Javascript Tags - Examples• <%= auto_discovery_link_tag(:rss, {:action => "feed"},{:title =>
"RSS Feed"}) %>• <%= javascript_include_tag "main", "columns", :cache => true
%>• <%= javascript_include_tag "http://example.com/main.js" %>• <%= javascript_include_tag :defaults %>• <%= javascript_include_tag :all, :recursive => true %>
131
Stylesheet Asset Tag - Examples• <%= stylesheet_link_tag "main", "/photos/columns" %>• <%= stylesheet_link_tag "http://example.com/main.css" %>• <%= stylesheet_link_tag "main_print", :media => "print" %>• <%= stylesheet_link_tag :all, :recursive => true %>• <%= stylesheet_link_tag "main", "columns", :cache => true %>
132
Image/Video/Audio Asset Tags Examples• <%= image_tag "home.gif", :onmouseover =>
"menu/home_highlight.gif", :alt => “Home” %>• <%= video_tag "movie.ogg", :poster
=>'movie_start.png', :autoplay => false, :loop => false, :controls => true, :autobuffer => true %>
• <%= audio_tag "music/first_song.mp3", :autoplay => false, :controls => true, :autobuffer => true %>
133
AssetTagHelper
• image_tag("rails.png")• <img alt="Rails" src="/images/rails.png?1230601161" />
• video_tag("trailer")• <video src="/videos/trailer" />
• audio_tag("sound.wav")• <audio src="/audios/sound.wav" />
134
TextHelper• Provides common-use text manipulation methods• http://api.rubyonrails.org/classes/ActionView/Helpers/
TextHelper.html• truncate("Once upon a time in a world far far away", :length => 17)• highlight('You searched for: rails', 'rails')• excerpt('This is an example', 'an', :radius => 5)• pluralize(2, 'person')• word_wrap('Once upon a time', 4)• simple_format("Here is some basic text...\n...with a line break.")• auto_link("Go to http://www.rubyonrails.org and say hello to
[email protected]")• <tr class="<%= cycle("even", "odd") -%>">
135
UrlHelper• Provides an easy way to create dynamic urls based on • http://api.rubyonrails.org/classes/ActionView/Helpers/UrlHelper.html
• url_for(:controller => 'posts',:action => 'show', :only_path => false) • http://localhost:3000/posts/show
• link_to "Other Site", "http://www.rubyonrails.org/", :confirm => "Sure?"
• link_to "Post", :controller => "posts", :action => "show", :id => @post• <a href="/posts/show/1">Post</a>
• link_to "Delete Post", { :controller => ‘posts’, :action => "delete", :id => 1 }, :method=> :delete• <a href='/posts/delete' rel="nofollow" data-method="delete"
data-confirm="Are you sure?">Delete Post</a>• link_to_unless_current("Home", { :action => "index" })
136
UrlHelper
• button_to "New", :action => "new"• <form method="post" action="/controller/new"
class="button_to">• <div><input value="New" type="submit" /></div>• </form>"
137
Protect Email Addresses
• Encoding emails makes it more difficult for web spiders to lift email addresses off of a site• mail_to "[email protected]", "My email", :encode =>
"javascript"• mail_to "[email protected]", "My email", :encode => "hex"
138
Form Helpers
• Rails deals away with complexities of form controls naming and their attributes by providing view helpers for generating form markup.
• Developers should know all the differences between similar helper methods before putting them to use as each has a different use case.
139
form_tag
• Without any params, the default path is current page and default method used is post.
• First parameter can be a path, or a hash of URL parameters• Second parameter is a set of options• <%= form_tag(search_path, :method => "get") do %>• <%= label_tag(:q, "Search for:") %>• <%= text_field_tag(:q) %>• <%= submit_tag("Search") %>• <% end %>• <%= form_tag({:controller => "people", :action =>
"search"}, :method => "get", :class => "nifty_form") do %>...<% end %>
140
*_tags• text_field_tag(:query)
• <input id="query" name="query" type="text" />• check_box_tag(:married)
• <input id="married" name="married" type="checkbox" value="1" />• radio_button_tag(:age, "child")
• <input id="age_child" name="age" type="radio" value="child" />• label_tag(:age_child, "I am younger than 21")
• <label for="age_child">I am younger than 21</label>• text_area_tag(:message, "Hi, nice site", :size => "24x6")
• <textarea id="message" name="message" cols="24" rows="6">Hi, nice site</textarea>
• password_field_tag(:password)• <input id="password" name="password" type="password" />
• hidden_field_tag(:parent_id, "5")• <input id="parent_id" name="parent_id" type="hidden" value="5" />
141
Form Helpers for Model• Editing or creating a particular model object is a common task for a form. • Rails provides helpers tailored to ensure the correct parameter name is
used for consumption by controller. • These helpers lack the _tag suffix, for example text_field, text_area.• The first argument is the name of an instance variable and the second
is the name of a method (usually an attribute) to call on that object.• text_field(:person, :name) #assuming controller has defined
@person• <input id="person_name" name="person[name]" type="text"
value="Hisham"/>• Upon form submission the value entered by the user will be stored
in params[:person][:name]. • The params[:person] hash is suitable for passing to Person.new or,
if @person is an instance of Person, @person.update_attributes. 142
Parameter Naming Convention• Rails converts name-value pairs of html forms to Hashes/Arrays
• <input id="person_name" name="person[name]" type="text" value="Hisham"/>• {'person' => {'name' => 'Henry'}}
• <input id="person_address_city" name="person[address][city]" type="text" value="Lahore"/>• {'person' => {'address' => {'city' => 'Lahore'}}}
• <input name="person[phone_number][]" type="text" value="0321"/><input name="person[phone_number][]" type="text" value="87654321"/>• {'person' => {'phone_number' => ['0321', '87654321']}}
• <input name="addresses[][line1]" type="text"/><input name="addresses[][line2]" type="text"/><input name="addresses[][city]" type="text"/>
143
Binding Form to Model Object: form_for• <%= form_for @article, :url => { :action => "create" }, :html =>
{:class => "nifty_form"} do |f| %>• <%= f.text_field :title %>• <%= f.text_area :body, :size => "60x12" %>• <%= submit_tag "Create" %>• <% end %>• First parameter can be name of model (:article), or model
object itself• :url and :html are optional params• If :url is not specified, then Rails automatically adjust for
between editing or creating based on whether model object is new or existing record (@article.new_record?) 144
fields_for• Generates proper names for nested attributes• <%= form_for @person do |person_form| %>• <%= person_form.text_field :name %>• <% for address in @person.addresses %>• <%= person_form.fields_for address, :index => address do |address_form|%>• <%= address_form.text_field :city %>• <% end %>• <% end %>• <% end %>• #output• <form action="/people/1" class="edit_person" id="edit_person_1" method="post">• <input id="person_name" name="person[name]" type="text" />• <input id="person_address_23_city" name="person[address][23][city]" type="text" />• <input id="person_address_45_city" name="person[address][45][city]" type="text" />• </form> 145
Select/Options
• select_tag(:city_id, '<option value="1">Lisbon</option>...')• select_tag(:city_id, options_for_select(...))
• options_for_select([['Lahore', 1], ['Karachi', 2], ...], 1) # last parameter is the selected option
• options_from_collection_for_select(City.all, :id, :name)• select(:person, :city_id, [['Lahore', 1], ['Karachi', 2], ...])
• selected option is the one that model object has• collection_select(:person, :city_id, City.all, :id, :name)
146
Date/Time/Timezone Helpers• time_zone_select(:person, :time_zone)• time_zone_options_for_select• select_date Date.today, :prefix => :birth_date• <select id="birth_date_year" name="birth_date[year]"> ...
</select>• <select id="birth_date_month" name="birth_date[month]"> ...
</select>• <select id="birth_date_day" name="birth_date[day]"> ...
</select>• date_select :person, :birth_date• select_time Time.now, :prefix => :arrival_time• time_select :flight, :arrival_time• select_datetime, datetime_select
147
Uploading Files• The most important thing to remember with file uploads is that the form’s encoding
MUST be set to “multipart/form-data”.• #view• <%= form_tag({:action => :upload}, :multipart => true) do %>• <%= file_field_tag 'picture' %>• <% end %>• <%= form_for @person, :html => {:multipart => true} do |f| %>• <%= f.file_field :picture %>• <% end %>• #controller• def upload• uploaded_io = params[:person][:picture]• File.open(Rails.root.join('public', 'uploads', uploaded_io.original_filename), 'w') do |
file|• file.write(uploaded_io.read)• end• end
148
Routing
149
Routing Options
• Resource Routing• Non-Resourceful Routes
150
Resource Routing
• Allows you to quickly declare all of the common routes for a given resourceful controller.
• Instead of declaring separate routes for your index, show, new, edit, create, update and destroy actions, a resourceful route declares them in a single line of code.
• resources :photos• resources :photos, :books, :videos
151
Paths and URLs
• Creating a resourceful route will also expose a number of helper functions in your application. • photos_path returns /photos• new_photo_path returns /photos/new• edit_photo_path(id) returns /photos/:id/edit (for instance,
edit_photo_path(10) returns /photos/10/edit)• photo_path(id) returns /photos/:id (for instance,
photo_path(10) returns /photos/10)• Each of these helpers has a corresponding _url helper (such as
photos_url) which returns the same path prefixed with the current host, port and path prefix.
152
Singular Resources
• Sometimes, you have a resource that clients always look up without referencing an ID.
• For example, you would like /profile to always show the profile of the currently logged in user.
• In this case, you can use a singular resource to map /profile (rather than /profile/:id) to the show action.
• resource :geocoder
153
Singular Resources Paths and URLs• A singular resourceful route generates these helpers:
• new_geocoder_path returns /geocoder/new• edit_geocoder_path returns /geocoder/edit• geocoder_path returns /geocoder
• As with plural resources, the same helpers ending in _url will also include the host, port and path prefix.
154
Nested Resources
• Common to have resources that are logically children of other resources.
• class Magazine < ActiveRecord::Base• has_many :ads• end
• class Ad < ActiveRecord::Base• belongs_to :magazine• end• resources :magazines do• resources :ads• end 155
Restricting Routes
• You can use the :only and :except options to fine-tune this behavior.• resources :photos, :only => [:index, :show]• resources :photos, :except => :destroy
156
Resource Scope and Name Collisions• Namespace allows you to to group a number of similar controllers together to in a sub-folder and avoid name collisions.
• For example, you might group a number of administrative controllers under an Admin:: namespace. You would place these controllers under the app/controllers/admin directory, and you can group them together in your router:
• namespace :admin do• resources :posts• end• functionality equivalent to:• scope :path => :admin, :module => :admin do• resources :posts• end• Following snippets will create route helpers such as admin_photos_path, new_admin_photo_path avoiding name collisions:• scope "admin" do• resources :photos, :as => "admin_photos"• end• resources :photos• scope "admin", :as => "admin" do• resources :photos, :accounts• end
157
Constraints
• You can use the :via option to constrain the request to one or more HTTP method• match 'photos/show' => 'photos#show', :via => [:get, :post]
• You can use the :constraints option to enforce a format for a dynamic segment• match 'photos/:id' => 'photos#show', :constraints => { :id
=> /[A-Z]\d{5}/ }• match 'photos/:id' => 'photos#show', :id => /[A-Z]\d{5}/
• You can also constrain a route based on any method on the Request object that returns a String• match "photos", :constraints => {:subdomain => "admin"}
158
Non-Resourceful Routes
• While you should usually use resourceful routing, there are still many places where the simpler routing is more appropriate.• match ':controller/:action/:id/:user_id'• match ':controller(/:action(/:id))', :controller =>
/admin\/[^\/]+/• Anything other than :controller or :action will be available to
the action as part of params• Possible to supply default :controller and :action
• match 'photos/:id' => 'photos#show'
159
Representational State Transfer (REST)• The uniform interface that any REST interface must provide is considered fundamental to the design
of any REST service.• Identification of resources
• Individual resources are identified in requests, for example using URIs in web-based REST systems. The resources themselves are conceptually separate from the representations that are returned to the client. For example, the server does not send its database, but rather, perhaps, some HTML, XML or JSON that represents some database records expressed.
• Manipulation of resources through these representations• When a client holds a representation of a resource, including any metadata attached, it has
enough information to modify or delete the resource on the server, provided it has permission to do so.
• Self-descriptive messages• Each message includes enough information to describe how to process the message. For example,
which parser to invoke may be specified by an Internet media type (previously known as a MIME type). Responses also explicitly indicate their cacheability.
• Hypermedia as the engine of application state• Clients make state transitions only through actions that are dynamically identified within
hypermedia by the server (e.g. by hyperlinks within hypertext). Except for simple fixed entry points to the application, a client does not assume that any particular actions will be available for any particular resources beyond those described in representations previously received from the server.
160
RESTful Resources
• Resources are typically ActiveRecord models and each 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 verbs• GET, POST, PUT, DELETE
• In REST we strive to have associations be join models so that they can be exposed as resources.
161
REST <-> CRUD ?
• In REST, it’s the HTTP verb (POST, GET, PUT, DELETE) that changes which action a particular request is routed to.
• Just because an application is RESTful does not mean it has to have a strict adherence to CRUD! You can map your actions however you like.
162
Resource GET PUT POST DELETE
Collection URI, such as http://example.com/resources/
List the URIs and perhaps other details of the collection's members.
Replace the entire collection with another collection.
Create a new entry in the collection. The new entry's URL is assigned automatically and is usually returned by the operation.
Delete the entire collection.
Element URI, such as http://example.com/resources/xyz
Retrieve a representation of the addressed member of the collection, expressed in an appropriate Internet media type.
Update the addressed member of the collection.
Treat the addressed member as a collection in its own right and create a new entry in it.
Delete the addressed member of the collection.
Handling of PUT and DELETE methods• Most browsers don’t support methods other than “GET” and “POST” when it
comes to submitting forms• Rails works around this issue by emulating other methods over POST with a
hidden input named"_method", which is set to reflect the desired method.• When parsing POSTed data, Rails will take into account the special _method
parameter and acts as if the HTTP method was the one specified inside it.• form_tag(search_path, :method => "put")
• <form action="/search" method="post">• <div style="margin:0;padding:0">• <input name="_method" type="hidden" value="put" />• <input name="authenticity_token" type="hidden"
value="f755bb0ed134b76c432144748a6d4b7a7ddf2b71" />• </div>• </form>
163
Route Globbing
• Specify that a particular parameter should be matched to all the remaining parts of a route.• match 'photos/*other' => 'photos#unknown'
• This route would match photos/12 or /photos/long/path/to/12, setting params[:other] to "12" or "long/path/to/12".
164
Optional Segments
• match 'posts(/new)', :to => 'posts#create'• Here, both /posts/new and /posts will be redirected to the
create action in posts controller
165
Empty Route
• The root of the web site is the empty route.• root :to => 'welcome#show'
166
Redirection
• match "/stories" => redirect("/posts")• match "/stories/:name" => redirect("/posts/%{name}")• match "/stories/:name" => redirect {|params|
"/posts/#{params[:name].pluralize}" }• match "/stories" => redirect {|p, req|
"/posts/#{req.subdomain}" }
167
Day 3
168
Testing
169
Testing
• Testing is about asserting that certain expectations come true – that code works the way you expect it to when it runs.
• Writing good tests means that you’ll find yourself • catching more bugs, • re-thinking your approach when you realize your code isn’t
easily testable (a red flag that maintenance down the line is going to be rough), and
• having something to tell you that your application isn’t completely broken after you’ve changed or added a feature.
170
Testing Workflow
• The typical workflow for testing looks like this:• Write a test or refactor an existing one• Run the test, watch it fail• Make your code pass all tests
• Polishing and refactoring your application becomes a matter of repeating these steps, with the added bonus of staying focused while you write code (all you have to do is pass the test) as well as keeping your code tested.
171
Rails Testing Terminology• Unit (Model)
• These test business logic in your models. • A well-written Rails application should have the bulk of its code in its
models, so the bulk of your tests should be these.• Functional (Controller)
• These test individual controller actions in isolation.• Integration (Controller to Controller)
• These test state mutations between/over multiple actions and routing, i.e.ensuring that things don’t totally explode as a user clicks through a typical work flow.
• Fixtures # will not be using these, will work with Factories instead• Used to hold example model data used to easily instantiate those models
in tests, avoiding the tedious process of manually creating model objects.• Unit/Helpers # will not be using these
• These test helpers used in views.172
RSpec• RSpec is a Behaviour-Driven Development tool for Ruby
programmers.• RSpec uses the general malleability of Ruby to define a domain-
specific language (DSL) built just for testing.• Add rspec to development and test environment of project in Gemfile• group :development do• gem 'webrat'• gem 'rspec-rails'• end• group :test do• gem 'webrat'• gem 'rspec-rails'• end• run bundle install
173
Sample RSpec Example
• # bowling_spec.rb• require 'bowling'• describe Bowling, "#score" do• it "returns 0 for all gutter game" do• bowling = Bowling.new• 20.times { bowling.hit(0) }• bowling.score.should == 0• end• end
• #run the test through command line• rspec bowling_spec.rb
174
Controller Testing
• Allows testing of controllers functions and expected responses• Generate a user controller with new action:• rails generate controller Users new• Develop test:
• When your action :new is called, the response should be a success
• Title should be ‘Sign up’
175
User Controller Testing• #spec/controllers/users_controller_spec.rb• require 'spec_helper'
• describe UsersController do• render_views #to render the associated view• describe "GET 'new'" do• it "should be successful" do• get 'new'• response.should be_success• end
• it "should have the right title" do• get 'new'• response.should have_selector("title", :content => "Sign up")• end• end• end
176
Pages Controller Testing
• When home method is called, response should be successful• When home is called, title should be ‘Ruby on Rails Workshop
Sample App | Home’• When contact page is called, response should be successful.• When contact page is called, title should be ‘Ruby on Rails
Workshop Sample App | Contact’• When about page is called, response should be successful. • When about page is called, title should be ‘Ruby on Rails
Workshop Sample App | About’
177
Do it yourself?
178
Pages Controller Testing• #Rails Tutorial Chapter 3• #spec/controllers/pages_controller_spec.rb• require 'spec_helper'
• describe PagesController do• render_views #necessary for titles test to work
• describe "GET 'home'" do• it "should be successful" do• get 'home'• response.should be_success• end• it "should have the right title" do• get 'home'• response.should have_selector("title",• :content => "Ruby on Rails Workshop Sample App | Home")• end• end
• describe "GET 'contact'" do• it "should be successful" do• get 'contact'• response.should be_success• end
• it "should have the right title" do• get 'contact'• response.should have_selector("title",• :content =>• "Ruby on Rails Workshop Sample App | Contact")• end• end
• describe "GET 'about'" do• it "should be successful" do• get 'about'• response.should be_success• end
• it "should have the right title" do• get 'about'• response.should have_selector("title",• :content =>• "Ruby on Rails Workshop Sample App | About")• end• end• end• # run all the tests• bundle exec rspec spec/
179
Code for Page Testing• #app/controllers/pages_controller.rb• #remove app/views/layouts/application.html.erb• class PagesController < ApplicationController• :layout false• def home• end• def contact• end• def about• end• end
• #app/views/pages/about.html.erb• <h1>Pages#about</h1>• <p>Find me in app/views/pages/about.html.erb</p>
• #config/routes.rb• SampleApp::Application.routes.draw do• get "pages/home"• get "pages/contact"• get "pages/about"• ...• end 180
Code for Title Testing• #app/views/pages/home.html.erb• <!DOCTYPE html>• <html>• <head>• <title>Ruby on Rails Workshop Sample App | Home</title>• </head>• <body>• <h1>Sample App</h1>• <p>• This is the home page for the Ruby on Rails Workshop sample application.• </p>• </body>• </html>
• #app/views/pages/contact.html.erb• <!DOCTYPE html>• <html>• <head>• <title>Ruby on Rails Workshop Sample App | Contact</title>• </head>• <body>• <h1>Contact</h1>• <p>• This is the contact page for the Ruby on Rails Workshop sample application.• </p>• </body>• </html>• #Similar approach for app/views/pages/about.html.erb as well
181
Integration Tests
• Give us a way to simulate a browser accessing our application and thereby test it from end to end.• rails generate integration_test layout_links
• Generates spec/requests/layout_links_spec.rb• Controller tests only know about URLs defined for that exact
controller whereas Integration Tests are bound by no such restriction.
182
Integration Testing Example
• When /home is called, title should be ‘Ruby on Rails Workshop Sample App | Home’
• When /contact page is called, title should be ‘Ruby on Rails Workshop Sample App | Contact’
• When /about page is called, title should be ‘Ruby on Rails Workshop Sample App | About’
183
Code for Integration Testing• #spec/requests/layout_links_spec.rb• require 'spec_helper'
• describe "LayoutLinks" do
• it "should have a Home page at '/'" do• get '/'• response.should have_selector('title', :content => "Home")• end
• it "should have a Contact page at '/contact'" do• get '/contact'• response.should have_selector('title', :content => "Contact")• end
• it "should have an About page at '/about'" do• get '/about'• response.should have_selector('title', :content => "About")• end
• it "should have a Help page at '/help'" do• get '/help'• response.should have_selector('title', :content => "Help")• end• end
• #config/routes.rb• SampleApp::Application.routes.draw do• match '/contact', :to => 'pages#contact'• match '/about', :to => 'pages#about'• match '/help', :to => 'pages#help'• ...• end
184
ModelTesting
• These test business logic in your models. • A well-written Rails application should have the bulk of its code
in its models, so the bulk of your tests should be these
185
RSpec before(:each/all)
• before(:each) runs the code inside the block before each test defined in the describe block
• before(:all) runs the code inside the block before all test defined in the describe block• before(:all) shares some (not all) state across multiple
examples. • Examples become bound together, which is an absolute no-
no in testing. • Only ever use before(:all) to set up things that are global
collaborators (expensive operation/setup) but not the things that you are describing in the examples.
186
User Model Testing
• User should be created successfully when passed valid attributes
187
User Model Testing• #spec/models/user_spec.rb• require 'spec_helper'• describe User do• before(:each) do• @attr = { name: "Hisham Malik", email: “[email protected]”,
password: "arkhitech", password_confirmation: "confiz" }• end
• it "should create a new instance given valid attributes" do• User.create!(@attr)• end• end
188
Have a test to write, but not ready yet(requirements missing)?
189
Pending/TODO Test• To indicate that we should fill the spec with something useful• #spec/models/user_spec.rb• require 'spec_helper'• describe User do• pending "add some examples to (or delete) #{__FILE__}"• it "should require a name"• end• #command-line• $ bundle exec rspec spec/models/user_spec.rb • Finished in 0.01999 seconds• 1 example, 0 failures, 1 pending• Pending:• User add some examples to (or delete)• /Users/hisham/projects/sample_app/spec/models/user_spec.rb• (Not Yet Implemented)• User should require a name (Not Yet Implemented)• /Users/hisham/projects/sample_app/spec/models/user_spec.rb:5
190
Ruby - Advance Topics
191
More On 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)
192
Method Aliasing
• class Peter• def say_hi• puts "Hi"• end• end• class Peter• alias_method :say_hi_orig, :say_hi• def say_hi• puts "Before say hi"• say_hi_orig• puts "After say hi"• end• end
193
Reflection
• “abc”.class # => String• “abc”.class.superclass # => Object• “abc”.class.ancestors # Lists implemented modules• “abc”.methods # => methods available for this instance• String.instance_methods(false) # => methods implemented in
String class only (not super classes)• 10.5.kind_of?(Numeric) # => true• 10.5.instance_of?(Float) # => true
194
Reflection
• method_missing• const_missing• instance_variable_get• instance_variable_set
195
Method Overriding/Overwriting• class Peter• def say_hi• puts "Hi"• end• end• class Peter• alias_method :say_hi_orig, :say_hi• def say_hi• puts "Before say hi"• say_hi_orig• puts "After say hi"• end• end
196
blocks, closures, andproc objects• def invoke_block• puts "before block"• yield 5• puts "after block"• end• invoke_block { |n| puts "In block and received #{n}"}• my_proc = Proc.new { |n| puts "In proc, received #{n}"}• my_proc.call 2 # => Procedure called with 2 as param• invoke_block &my_proc # Procedure called with 5 as param
197
Blocks – Usage Examples
• Iteration• [1, 2, 3].each {|item| puts item }
• Resource Management• file_contents = open(file_name) { |f| f.read }
• Callbacks• widget.on_button_press do• puts “Got button press”• end
• Convention: • one-line blocks use {...} • multiline blocks use do...end 198
Active Records Contd.
199
Conditions
• Hash Conditions• Client.where(:locked => true)• Client.where(:created_at => (params[:start_date].to_date)..
(params[:end_date].to_date))• Client.where(:orders_count => [1,3,5])
200
Order, Select, Limit, Offset, Group, and Having• Order
• Client.order(‘created_at’)• Client.order(‘created_at DESC’)
• Select• Client.select(‘DISTINCT(name)’)
• Limit• Client.limit(5)• SELECT * FROM clients LIMIT 5
• Offset• Client.limit(5).offset(30)• SELECT * FROM clients LIMIT 5, 30
• Group• Order.group(‘date(created_at)’).order(‘created_at’)• SELECT * FROM orders GROUP BY Date(created_at) ORDER BY created_at
• Having• Order.group("date(created_at)").having("created_at > ?", 1.month.ago)• SELECT * FROM orders GROUP BY date(created_at) HAVING created_at > '2009-01-15'• This will return single order objects for each day, but only for the last month.
201
Dynamic Finder
• ActiveRecord provides a finder method for every field (also known as an attribute) that you define in your table. • Client.find_by_first_name(‘Hisham’)• Client.find_by_company!(‘Confiz’) #throws an
RecordNotFound Exception if no record is returned• Client.find_all_by_company(‘Confiz’)
• Allow searching over multiple fields.• Client.find_by_first_name_and_company(‘Hisham’,‘Confiz’)
202
Dynamic Finders/Initializers• ActiveRecord allows you to find or create/initialize objects if they aren’t
found.• Client.find_or_create_by_first_name(‘Hisham’)
• SELECT * FROM clients WHERE (clients.first_name = 'Hisham') LIMIT 1• BEGIN• INSERT INTO clients (first_name, updated_at, created_at,
orders_count, locked)• VALUES('Hisham', '2008-09-28 15:39:12', '2008-09-28 15:39:12', 0, '0')• COMMIT
• Client.find_or_initialize_by_first_name('Hisham')• You can modify other fields in client by calling the attribute setters
on it: client.company = ‘Confiz’ and when you want to write it to the database just call save on it.
203
find_by_sql/select_all
• Client.find_by_sql("SELECT * FROM clients INNER JOIN orders ON clients.id = orders.client_id ORDER clients.created_at desc")• Returns an array of clients
• Client.connection.select_all("SELECT * FROM clients WHERE id = '1'")• Returns an array of Hashes, where each hash represents a
record
204
Find with Conditions
Member.all(:conditions => {:last_name => 'malik'}) Member.all(:conditions => [“expiry_data <= ?”, Time.now)Member.all(:conditions => [“nick_name like ?”,”#{params[:nick_name]}%”])Member.all(:conditions => [“dob <= ?”, 20.years.ago], :limit => 10, :offset => 100, :order => :id)
205
Locking
account = Account.lock.find(id)account = Account.find(id, :lock => true)
SELECT * FROM accounts WHERE (account.`id` = 1) FOR UPDATE
account.balance -= 10 account.save!
206
Update/Create
Member.create(:nick_name => 'Salaar', expiry_date => Time.now + 1.year) member.nick_name = 'Mikael'member.save!
207
Destroy/Delete
Destroy instantiates object and calls all hooksDelete simply executes the delete query
208
Available Callbacks
• Creating/Updating an Object• before_validation• after_validation• before_save• after_save
• Destroying an Object• before_destroy• after_destroy• around_destroy
209
Transactions
•Account.transaction do account1.deposit(100) account2.withdraw(100)endAccount.transaction(account1, account2) do account1.deposit(100) account2.withdraw(100)end
210
Calculations
Person.minimum(‘age’)Person.maximum(‘age’)Person.sum(‘age’)Person.where([“age > ?”, 25]).countPerson.average(‘age’)
211
ActiveRecord Associations
• has_one: User has one profile• has_many: User has many posts• belongs_to: Post belongs to user• has_and_belongs_to: Post belongs to many categories, each
category has many posts
212
Why Associations?
• Allows to define associations between different models. For example:
• Firm has many Customers • Customer has many Orders• Order belongs to Customer
213
Types of Associations
• belongs_to• has_one• has_many• has_many :through• has_one :through• has_and_belongs_to_many
214
Example
• class Customer < ActiveRecord::Base• has_many :orders, :dependent => :destroy• end• class Order < ActiveRecord::Base• belongs_to :customer• end• @order = @customer.orders.create(:order_date => Time.now)• @customer.destroy
215
Belongs To
• A belongs_to association sets up a one-to-one connection with another model, such that each instance of the declaring model “belongs to” one instance of the other model.
216
Has One
• A has_one association also sets up a one-to-one connection with another model, but with somewhat different semantics (and consequences). This association indicates that each instance of a model contains or possesses one instance of another model.
217
Difference Between Belongs to and Has One?• The distinction is in where you place the foreign key.• Foreign key goes on the table for the class declaring the
belongs_to association.
218
Has Many
• Indicates a one-to-many connection with another model. Indicates that each instance of the model has zero or more instances of another model.
219
Has Many :Through
• Often used to set up a many-to-many connection with another model.
• Indicates that the declaring model can be matched with zero or more instances of another model by proceeding through a third model.
220
Has One :Through
• Sets up a one-to-one connection with another model. • This association indicates that the declaring model can be
matched with one instance of another model by proceeding through a third model.
221
Has and Belongs to Many
• Creates a direct many-to-many connection with another model, with no intervening model.
222
Has and Belongs to Many DB Migration• class CreateAssemblyPartJoinTable < ActiveRecord::Migration• def self.up• create_table :assemblies_parts, :id => false do |t|• t.integer :assembly_id• t.integer :part_id• end• end• • def self.down• drop_table :assemblies_parts• end• end
223
Passed :id => false to create_table because that table does not represent a model.Required for the association to work properly.
Polymorphic Associations
• A model can belong to more than one other model, on a single association
• For example, a picture can belong to a user or a product.
224
Creating Polymorphic DB Migration• class CreatePictures < ActiveRecord::Migration• def self.up• create_table :pictures do |t|• t.string :name• t.references :imageable, :polymorphic => true• t.timestamps• end• end• • def self.down• drop_table :pictures• end• end• class CreatePictures < ActiveRecord::Migration• def self.up• create_table :pictures do |t|• t.string :name• t.integer :imageable_id• t.string :imageable_type• t.timestamps• end• end• def self.down• drop_table :pictures• end• end
225
functionally equivalent
functionally equivalent
Methods Added by Belongs To/Has One• association(force_reload = false)
• Returns the associated object, nil if none found. • Set force_reload = true, to not use cached object
• association=(associate)• Assigns an associated object to this object.
• build_association(attributes = {})• Returns a new object of the associated type instantiated with
passed attributes.• Foreign key relation is also set, but not saved yet.
• create_association(attributes = {})• Returns a new object of the associated type instantiated with
passed attributes.• Foreign key relation is also set, and object is saved if it passes
validations.
226
Auto-Saving by Has One
• When you assign an object to a has_one association, that object is automatically saved (in order to update its foreign key). In addition, any object being replaced is also automatically saved, because its foreign key will change too.
• If either of these saves fails due to validation errors, then the assignment statement returns false and the assignment itself is cancelled.
• If the parent object (the one declaring the has_one association) is unsaved (that is, new_record? returns true) then the child objects are not saved. They will automatically when the parent object is saved.
• If you want to assign an object to a has_one association without saving the object, use the association.build method.
227
Methods Added by Has Many/Has and Belongs to Many• collection(force_reload = false)
• Returns the associated collection, empty array if none found. • Set force_reload = true, to not use cached collection
• collection<<(object, …)• Adds one more object to the collection
• collection.delete(object, …)• Deletes one or more objects by setting their foreign keys to NULL
• collection=objects• Makes the collection contain only the supplied objects, by adding and
deleting as appropriate.• collection_ids
• Returns array of ids of collection, empty array if none found. • collection_ids=ids
• Makes the collection contain only the objects identified by the ids, by adding and deleting as appropriate.
228
Methods Added by Has Many/Has and Belongs to Manycollection.clear
Removes every object from the collectioncollection.empty?collection.sizecollection.find(…)
Finds objects within the collection.collection.exists?(…)
Checks for existence within collection.collection.build(attributes = {}, …)
Returns one or more new objects of the associated type.collection.create(attributes = {})
Returns a new object of the associated type229
Auto-Saving by Has Many/Has and Belongs to ManyWhen you assign an object to a has_many association, that object is automatically saved (in order to update its foreign key). If you assign multiple objects in one statement, then they are all saved.If any of these saves fails due to validation errors, then the assignment statement returns false and the assignment itself is cancelled.If the parent object (the one declaring the has_many association) is unsaved (that is, new_record? returns true) then the child objects are not saved when they are added. All unsaved members of the association will automatically be saved when the parent is saved.If you want to assign an object to a has_many association without saving the object, use the collection.build method.
230
Joins
• Join using String• Client.joins('LEFT OUTER JOIN addresses ON
addresses.client_id = clients.id')• SELECT clients.* FROM clients LEFT OUTER JOIN addresses
ON addresses.client_id = clients.id• Using Array/Hash of Named Associations (Works with INNER
JOIN Only)• Post.joins(:category, :comments)• SELECT posts.* FROM posts• INNER JOIN categories ON posts.category_id = categories.id• INNER JOIN comments ON comments.post_id = posts.id• Category.joins(:posts => [{:comments => :guest}, :tags])
231
Joining and Searching
• Group.joins(:group_members).where(:name=>'ror-training',:group_members=>{:is_muted => false})
232
ValidationValidations are used to ensure that only valid data is saved into your database.
233
Client-Side Validations
• Client-side validations can be useful, but are generally unreliable if used alone.
• If they are implemented using JavaScript, they may be bypassed if JavaScript is turned off in the user’s browser.
• When combined with other techniques, client-side validation can be a convenient way to provide users with immediate feedback as they use your site.
234
Controller-level Validations
• Controller-level validations can be tempting to use, but often become unwieldy and difficult to test and maintain.
• Whenever possible, it’s a good idea to keep your controllers skinny, as it will make your application a pleasure to work with in the long run.
235
Model-Level Validation
• Model-level validations are the best way to ensure that only valid data is saved into your database.
• They are database agnostic, cannot be bypassed by end users, and are convenient to test and maintain. Rails makes them easy to use, provides built-in helpers for common needs, and allows you to create your own validation methods as well.
236
Database Validations
• Database constraints and/or stored procedures make the validation mechanisms database-dependent and can make testing and maintenance more difficult.
• If your database is used by other applications, it may be a good idea to use some constraints at the database level.
• Database-level validations can safely handle some things (such as uniqueness in heavily-used tables) that can be difficult to implement otherwise.
237
ActiveRecord Validation Triggerscreatecreate!savesave!updateupdate_attributesupdate_attributes!The bang versions (e.g. save!) raise an exception if the record is invalid. The non- bang versions don’t: save and update_attributes return false, create and update just return the objects.
238
ActiveRecord Validation Skippingsave(:validate => false)decrement!decrement_counterincrement!increment_countertoggle!touchupdate_allupdate_attributeupdate_columnupdate_counters 239
ActiveRecord Valid?
class Person < ActiveRecord validates :name, :presence => trueend
Person.create(:name => "John Doe").valid? # => truePerson.create(:name => nil).valid? # => falsePerson.new(:name => nil).valid? # => false
240
ActiveRecord Errors
• To verify whether or not a particular attribute of an object is valid, you can use errors[:attribute].
• It returns an array of all the errors for :attribute. • If there are no errors on the specified attribute, an empty array
is returned.
241
Object-Level Errors
• You can add error messages that are related to the object’s state as a whole, instead of being related to a specific attribute in errors[:base].
• You can use this method when you want to say that the object is invalid, no matter the values of its attributes.
• class Person < ActiveRecord::Base• def a_method_used_for_validation_purposes• errors[:base] << "This person is invalid because ..."• end• end
242
Validation Helpers• Active Record offers many pre-defined validation helpers that provide
common validation rules. • Every time a validation fails, an error message is added to the object’s
errors collection.• Each helper accepts an arbitrary number of attribute names, so with
a single line of code you can add the same kind of validation to several attributes.
• All of them accept the :on and :message options, which define when the validation should be run and what message should be added to the errors collection if it fails, respectively.
• The :on option takes one of the values :save (the default), :create or :update.
• There is a default error message for each one of the validation helpers. These messages are used when the :message option isn’t specified.
243
validates_presence_of
• Validates that the specified attributes are not empty.• class Person < ActiveRecord::Base• validates_presence_of :name, :login, :email• end
244
validates_uniqueness_of
• Validates that the attribute’s value is unique right before the object gets saved.• Not guaranteed due to race conditions, so use together
unique database index on the attribute.• class Holiday < ActiveRecord::Base• validates_uniqueness_of :name, :scope => :year,• :message => "should happen once per year"• end
245
validates_acceptance_of
• Validates that a checkbox on the user interface was checked when a form was submitted.
• This validation is very specific to web applications and this ‘acceptance’ does not need to be recorded anywhere in your database.
• class Person < ActiveRecord::Base• validates_acceptance_of :terms_of_service• end
246
validates_confirmation_of• This validation creates a virtual attribute whose name is the name of
the field that has to be confirmed with “_confirmation” appended.• This check is performed only if email_confirmation is not nil. To
require confirmation, make sure to add a presence check for the confirmation attribute.
• <%= text_field :person, :email %>• <%= text_field :person, :email_confirmation %>• :• class Person < ActiveRecord::Base• validates_confirmation_of :email• validates_presence_of :email_confirmation• end
247
validates_format_of
• Validates the attributes’ values by testing whether they match a given regular expression, which is specified using the :with option.
• class Product < ActiveRecord::Base• validates_format_of :legacy_code, :with => /\A[a-zA-Z]+\z/,• :message => "Only letters allowed"• end
248
validates_length_of• Validates the length of the attributes’ values• The possible length constraint options are:
• :minimum – The attribute cannot have less than the specified length.• :maximum – The attribute cannot have more than the specified length.• :in (or :within) – The attribute length must be included in a given interval.
The value for this option must be a range.• :is – The attribute length must be equal to the given value.
• The default error messages depend on the type of length validation being performed. You can personalize these messages using the :wrong_length, :too_long, and :too_short options and %{count} as a placeholder for the number corresponding to the length constraint being used.
• class Person < ActiveRecord::Base• validates_length_of :bio, :maximum => 1000,• :too_long => "%{count} characters is the maximum allowed"• end
249
validates_numericality_of• Validates that your attributes have only numeric values.• Accepts the following options to add constraints to acceptable values:
• :only_integer - Specifies the value must be only integral values.• :greater_than – Specifies the value must be greater than the supplied value. • :greater_than_or_equal_to – Specifies the value must be greater than or equal
to the supplied value. • :equal_to – Specifies the value must be equal to the supplied value. • :less_than – Specifies the value must be less than the supplied value. • :less_than_or_equal_to – Specifies the value must be less than or equal the
supplied value. • :odd – Specifies the value must be an odd number if set to true. • :even – Specifies the value must be an even number if set to true.
• class Player < ActiveRecord::Base• validates_numericality_of :points• validates_numericality_of :games_played, :only_integer => true• end
250
Conditional Validation
• Validate an object just when a given predicate is satisfied• class Order < ActiveRecord::Base• validates_presence_of :card_number, :if => :paid_with_card? • def paid_with_card?• payment_type == "card"• end• end
251
Creating Custom Validation Methods• Simply create methods that verify the state of your models and add messages to the
errors collection when they are invalid. • You must then register these methods by using one or more of the validate,
validate_on_create or validate_on_update class methods, passing in the symbols for the validation methods’ names.
• class Invoice < ActiveRecord::Base• validate :expiration_date_cannot_be_in_the_past,• :discount_cannot_be_greater_than_total_value• def expiration_date_cannot_be_in_the_past• errors.add(:expiration_date, "can't be in the past") if• !expiration_date.blank? and expiration_date < Date.today• end• def discount_cannot_be_greater_than_total_value• errors.add(:discount, "can't be greater than total value") if• discount > total_value• end• end
252
Displaying Validation Errors• error_messages - to render all failed validation messages for the current model instance.• <%= form_for(@product) do |f| %>• <%= f.error_messages %>• <p>• <%= f.label :description %><br />• <%= f.text_field :description %>• </p>• <p>• <%= f.label :value %><br />• <%= f.text_field :value %>• </p>• <p>• <%= f.submit "Create" %>• </p>• <% end %> 253
Scaffolding for generates public/stylesheets/scaffold.css, which defines the red-based style.
Validation Errors CSS
• The selectors to customize the style of error messages are:• .field_with_errors – Style for the form fields and labels with
errors.• #errorExplanation – Style for the div element with the error
messages.• #errorExplanation h2 – Style for the header of the div
element.• #errorExplanation p – Style for the paragraph that holds the
message that appears right below the header of the div element.
• #errorExplanation ul li – Style for the list items with individual error messages. 254
Live Demo - Contd
255
Refactoring View
• app/views/posts/show.html.erb is getting long• Solution?
• Comments displayed should be separated out• Comments form should be separated out
256
Refactoring View• app/views/posts/show.html.erb is getting long• Partials can be employed to refactor comments out
(app/views/comments/_comment.html.erb)• <p>• <b>Commenter:</b>• <%= comment.commenter %>• </p> • <p>• <b>Comment:</b>• <%= comment.body %>• </p>
• Update app/views/posts/show.html.erb• <h2>Comments</h2>• <%= render :partial => "comments/comment",• :collection => @post.comments %>
257
Refactoring View• Comments form should also be moved to its own partial (app/views/comments/_form.html.erb)• <%= form_for([@post, @post.comments.build]) do |f| %>• <div class="field">• <%= f.label :commenter %><br />• <%= f.text_field :commenter %>• </div>• <div class="field">• <%= f.label :body %><br />• <%= f.text_area :body %>• </div>• <div class="actions">• <%= f.submit %>• </div>• <% end %>• Update app/views/posts/show.html.erb• <h2>Add a comment:</h2>• <%= render "comments/form" %> 258
Can view, and add comments. What about delete?
259
Deleting Comments• Add link to delete comment in app/views/comments/_comment.html.erb• <p>• <%= link_to 'Destroy Comment', [comment.post, comment],• :confirm => 'Are you sure?',• :method => :delete %>• </p>• Add destroy method in CommentsController
(app/controller/comments_controller)• def destroy• @post = Post.find(params[:post_id])• @comment = @post.comments.find(params[:id])• @comment.destroy• redirect_to post_path(@post)• end 260
Associated comments should get deleted when post is deleted?
261
Dependent Destroy
• If you delete the post, then all associated comments should also get deleted.
• To achieve this, we can cascade the destruction of associated models.
• Modify the Post model, app/models/post.rb, to have dependent destroy of comments.
• has_many :comments, :dependent => :destroy
262
Multi-Model Form
• Rails supports interacting with more than one model from a single form.
• Supports nested attributes for a referencing model within the same form
• For example, we want to tag a post when creating or editing.
263
Creating Tags
• Create a model to hold Tags• rails generate model tag name:string post:references• Define association in Post model and specify that we want to
accept nested attributes for Tag Model.• has_many :tags• accepts_nested_attributes_for :tags, :allow_destroy
=> :true, :reject_if => :all_blank?• Render a partial in app/views/posts/_form.html to make a tag• <% @post.tags.build %>• <%= render :partial => 'tags/form', :locals => {:form =>
post_form} %>264
Creating Tags• Create partial app/views/tags/_form.html.erb containing the form
fields for tags• <%= form.fields_for :tags do |tag_form| %>• <div class="field">• <%= tag_form.label :name, 'Tag:' %>• <%= tag_form.text_field :name %>• </div>• <% unless tag_form.object.nil? || tag_form.object.new_record? %>• <div class="field">• <%= tag_form.label :_destroy, 'Remove:' %>• <%= tag_form.check_box :_destroy %>• </div>• <% end %>• <% end %>
265
Displaying Tags• Show associated tags for the posts (app/views/posts/show.html)• <p>• <b>Tags:</b>• <%= @post.tags.map { |t| t.name }.join(", ") %>• </p>
• Use View Helpers Instead to make code more cleaner• # app/helpers/posts_helper.rb• module PostsHelper• def join_tags(post)• post.tags.map { |t| t.name }.join(", ")• end• end• #app/views/posts/show.html• <p>• <b>Tags:</b>• <%= join_tags(@post) %>• </p>
266
AJAX
267
Introduction
• AJAX stands for Asynchronous JavaScript and XML• AJAX uses an XMLHttpRequest object that does HTTP requests
in the background. This avoids full page reloads and instead update parts of the page.
• This makes the application more responsive and interactive.
268
Suitable Applications of Ajax
• Post something in the background and add it to a list on the page
• In-Place form editing• Autocompletion of a text field• Drag-and-drop sortable lists• Live searching
269
Environment
• Notice app/views/layouts/application.html.erb• <%= javascript_include_tag :defaults %>
• public/javascripts• application.js• controls.js• dragdrop.js• effects.js• prototype.js• rails.js
270
Unobtrusive Javascript Background• Before CSS, the presentation of HTML elements was defined
inline.• Then CSS come, and the document presentation attributes
were slowly moved outside the main HTML document.• Something similar is happening now with HTML and JavaScript.
• the most common way to append a JavaScript function was to use the HTML-Javascript bridges such as the onevent attributes.
• <a href="javascript:void(0);" onclick="alert('Thanks for clicking me');">Click me</a>
271
Unobtrusive Javascript
• With the huge adoption of JavaScript programming, HTML documents fall again into the trap of mixing presentation/interaction elements within the page content and structure.
• The essence of the Unobtrusive JavaScript technique is to define the JavaScript interaction in a separate behavior layer.
• <a href="/domains/1" data-confirm="Are you sure?" data-method="delete" rel="nofollow">delete</a>
272
UJS Benefits
• Separation of the behavior from the markup (as happened for CSS with the separation of the presentation from the markup)
• Reusable code (both HTML and JavaScript code)• Better cache support• Supporting Progressive enhancement by using web
technologies in a layered fashion that allows everyone to access the basic content and functionality of a web page.
• Allows developers to write code much more easier to maintain and debug
273
Prototype -> JQuery
• JQuery is a more popular AJAX library and become the default in edge Rails (> 3.1).
• Setup Rails to use JQuery• Add gem 'jquery-rails', '>= 1.0.12' to Gemfile and run bundle
install• rails generate jquery:install --ui
• copy http://ajax.googleapis.com/ajax/libs/jqueryui/1.8/themes/base/jquery-ui.css to public/stylesheets/
274
Posting Comments Through AJAX• Update view to do AJAX form submit
(app/views/comments/_form.html.erb)• Update CommentsController to respond to javascript• Update show post template to
• encapsulate comments in a container (div) that javascript can reference.
• create a container where notice for comment creation can be displayed
• Create create comment javascript template that• appends the new comment to the comments container
defined• displays the flash notice in the container for notice• clears the comments form
275
Posting Comments Through AJAX• Update view to do AJAX form submit
(app/views/comments/_form.html.erb)• <%= form_for([@post, @post.comments.build], :remote =>
true) do |f| %>• Update CommentsController to respond to javascript• flash[:notice] = "Thanks for commenting!"• respond_to do |format|• format.html {redirect_to post_path(@post)}• format.js• end
276
Posting Comments Through AJAX• Encapsulate code app/views/comments/_comment.html.erb
within div that will be updated through AJAX (optional)• <div id=comment_<%=comment.id%>>• ...• </div>• Define divs in app/views/posts/show.html.erb that will be
updated through AJAX• <div id="comments">• <%= render :partial => "comments/comment", :collection =>
@post.comments %>• </div>• <div id="comment-notice"></div>
277
Posting Comments Through AJAX• Create javascript view to do some rendering and apply effects
(app/views/comments/create.js.erb)• /* Insert a notice between the last comment and the comment form */• $("#comment-notice").html('<div class="flash notice"><%=
escape_javascript(flash.delete(:notice)) %></div>');
• /* Add the new comment to the bottom of the comments list */• $("#comments").append("<%= escape_javascript(render(@comment))
%>");
• /* Highlight the new comment */• $("#comment_<%= @comment.id %>").effect("highlight", {}, 3000);
• /* Reset the comment form */• $("#new_comment")[0].reset();
278
Deleting Comments Through AJAX• Update view to make an AJAX call for delete
(app/views/comments/_comment.html.erb)• Update CommentsController to respond to javascript• Create destroy javascript template for comments to apply some
effects
279
Deleting Comments Through AJAX• Update view to make an AJAX call for delete
(app/views/comments/_comment.html.erb)• <%= link_to 'Remove Comment', [comment.post, comment],• :confirm => 'Are you sure?', :method => :delete, :remote => true %>• Update CommentsController to respond to javascript• respond_to do |format|• format.html {redirect_to post_path(@post)}• format.js• end• Create destroy javascript template for comments to apply some effects• /* Eliminate the comment by fading it out */• $('#comment_<%= @comment.id %>').fadeOut();
280
Deleting Post Through Ajax (Optional)• Create Javascript handler public/javascripts/posts.js• $(function() {• $('.delete_post').bind('ajax:success', function() { • $(this).closest('tr').fadeOut(); • });• });• Update view to include the javascript file• <% content_for :head do %>• <title>Posts</title>• <%= javascript_include_tag 'posts'%>• <% end %>• Update view to make an AJAX call for delete (app/views/posts/index.html.erb)• <%= link_to 'Destroy', post, :confirm => 'Are you sure?', :method => :delete, :remote => true, :class =
‘delete_post’ %>• Update PostsController’s destroy method to respond to javascript with no body. • respond_to do |format|• format.html { redirect_to(posts_url) } • format.js { render :nothing => true } • end
281
Security
282
Password Encryption• Rather than storing a raw password in the database (known as
“cleartext”), we store a string generated using a cryptographic hash function, which is essentially irreversible,
• Even an attacker in possession of the hashed password will be unable to infer the original!
• #try in rails console• require 'digest'• password = 'secret'• encrypted_password = Digest::SHA2.hexdigest(secret)• If an attacker ever got hold of the encrypted password, they would still
have a chance at discovering the originals (known as rainbow attack).• For example, attacker guesses that we used SHA2, and writes a
function to compare a given hash to the hashed values of potential passwords using perhaps english dictionary in combination with other brute force techniques which itself becomes a library of potential passwords!
283
Password Encryption with Salt• Create on the fly value (using Time perhaps), join it with the
original password and encrypt it to create ‘salt’.• Add the ‘salt’ to the original password and encrypt it again.• Result is a unique encrypted password which is unique to your
deployment only.• salt = Digest::SHA2.hexdigest("#{Time.now.utc}--#{password}")• encrypted_password = Digest::SHA2.hexdigest(("#{salt}--
#{password}")• Both salt and encrypted_password are stored in db so that
provided password can be checked.
284
Model Testing - Password Encryption• #spec/models/user_spec.rb• require 'spec_helper'• describe User do• ...• describe "password encryption" do
• before(:each) do• @user = User.create!(@attr)• end
• it "should have an encrypted password attribute" do• @user.should respond_to(:encrypted_password)• end
• it "should set the encrypted password" do• @user.encrypted_password.should_not be_blank• end• end• end
285
Model Testing - Has Password• #spec/models/user_spec.rb• require 'spec_helper'• describe User do• ...• describe "password encryption" do• before(:each) do• @user = User.create!(@attr)• end• ...• describe "has_password? method" do• ...• it "should be true if the passwords match" do• @user.has_password?(@attr[:password]).should be_true• end
• it "should be false if the passwords don't match" do• @user.has_password?("#{@attr[:password]}-invalid").should be_false• end • end• end• end
286
Secure Password Implementation• $ rails generate migration add_password_and_salt_to_users salt:string encrypted_password:string
• #app/models/user.rb• require 'digest'• class User < ActiveRecord::Base• ...• before_save :encrypt_password• ...• # Return true if the user's password matches the submitted password.• # Compares encrypted_password with the encrypted version of submitted_password. • def has_password?(submitted_password)• encrypted_password == encrypt(submitted_password)• end
• private• def encrypt_password• self.salt = make_salt if new_record?• self.encrypted_password = encrypt(password)• end
• def encrypt(string)• Digest::SHA2.hexdigest("#{salt}--#{string}")• end
• def make_salt• Digest::SHA2.hexdigest("#{Time.now.utc}--#{password}")• end• end
287
Autheticate Method
• Should take in email and password.• Returns the user if password matched, nil otherwise
288
Model Testing - Authenticate• describe User do• ...• describe "password encryption" do• ...• describe "authenticate method" do• it "should return nil on email/password mismatch" do• wrong_password_user = User.authenticate(@attr[:email], "wrongpass")• wrong_password_user.should be_nil• end
• it "should return nil for an email address with no user" do• nonexistent_user = User.authenticate("[email protected]", @attr[:password])• nonexistent_user.should be_nil• end
• it "should return the user on email/password match" do• matching_user = User.authenticate(@attr[:email], @attr[:password])• matching_user.should == @user• end• end• end• end 289
Autheticate Method Implementation• class User < ActiveRecord::Base• ...• def self.authenticate(email, submitted_password)• user = find_by_email(email)• return nil if user.nil?• return user if user.has_password?(submitted_password)• end• private• ...• end
290
Log Files Security Breach
• Even though we went to great pains to encrypt the password, both the password and its confirmation *may* have appeared as clear in the debug information
• If anyone ever got a hold of the file, they would potentially obtain the passwords for every user on the system!
• #log/development.log• Parameters: {"commit"=>"Sign up", "action"=>"create",• "authenticity_token"=>"K1HchFF8uYE8ZaQKz5DVG9vF2KGoXJu4J
Gp/VE3NMjA=",• "controller"=>"users",• "user"=>{"name"=>"Foo Bar",
"password_confirmation"=>"[FILTERED]",• "password"=>"[FILTERED]", "email"=>"foo@invalid"}}
291
Filtering Parameter Logging
• Rails provides mechanism to filter defined parameters from the log file.
• By default, all password attributes are filtered automatically in Rails 3.• notice setting in config/application.rb• config.filter_parameters += [:password]
• Any other parameter that need filtering (example, credit card information), should be added into the array
292
Day 4
293
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• Secure the cookie so that value in it is not exposed:
• cookies.signed[:remember_token] = user.id
294
Sessions
• A hash stored on the server, typically in a database table or in the file system.
• Keyed by the cookie _session_id that expires upon browser close.
• Avoid storing complex Ruby objects, instead put ids in the session and keep data in the database, i.e. use session[:user_id] rather than session[:user]
295
Implementing Sign-in/Sign-out w/ Remember• Allow user to sign-in/sign-out without using session• Demonstrate use of secure cookies to implement sign-in that
lasts even after browser close.
296
Sessions Helper• #app/helpers/sessions_helper.rb• module SessionsHelper• def signed_in?• !current_user.nil?• end• def sign_in(user)• cookies.permanent.signed[:remember_token] = [user.id, user.salt]• self.current_user = user• end• def sign_out• cookies.delete(:remember_token)• self.current_user = nil• end• def current_user=(user)• @current_user = user• end• def current_user• @current_user ||= user_from_remember_token• end• private
• def user_from_remember_token• User.authenticate_with_salt(*remember_token)• end
• def remember_token• cookies.signed[:remember_token] || [nil, nil]• end
• end
297
Sessions Controller• $rails generate controller Sessions new• #config/routes.rb• SampleApp::Application.routes.draw do• resources :users• resources :sessions, :only => [:new, :create, :destroy]
• match '/signup', :to => 'users#new'• match '/signin', :to => 'sessions#new'• match '/signout', :to => 'sessions#destroy'• ...• end
• #app/views/sessions/new.html.erb• <h1>Sign in</h1>
• <%= form_for(:session, :url => sessions_path) do |f| %>• <div class="field">• <%= f.label :email %><br />• <%= f.text_field :email %>• </div>• <div class="field">• <%= f.label :password %><br />• <%= f.password_field :password %>• </div>• <div class="actions">• <%= f.submit "Sign in" %>• </div>• <% end %>• <p>New user? <%= link_to "Sign up now!", signup_path %></p>
298
Sessions Controller Example• #app/controllers/sessions_controller• class SessionsController < ApplicationController• include SessionsHelper• def new• render 'new'• end
• def create• user = User.authenticate(
• params[:session][:email],• params[:session][:password])• if user.nil?• flash.now[:error] = "Invalid email/password combination."• @title = "Sign in"• render 'new'• else• sign_in user• redirect_to user• end• end
• def destroy• sign_out• redirect_to root_path• end• end
299
Including SessionsHelper
• By default, all the helpers are available in the views but not in the controllers.
• We need the methods from the Sessions helper in both places, so we have to include it explicitly
300
Authenticate Method
• #app/model/user.rb• class User < ActiveRecord• def self.authenticate_with_salt(id, cookie_salt)• user = find_by_id(id)• (user && user.salt == cookie_salt) ? user : nil• end• end
301
Capistrano
302
Capistrano
• Capistrano is a wonderful program for deploying Rails 3 apps.• Allows for great customization and is very simple to use. • It is very flexible and will work with almost any version control
system out there.
303
Setup Capistrano
• Install Capistrano with the following command:• gem install capistrano
• Open up your Gemfile and add:• gem 'capistrano'
• Initialize Capistrano for project by running:• capify .
• This creates two files Capfile and config/deploy.rb
304
Noteworthy Settings
• default_run_options[:pty] = true # Must be set for the password prompt from git to work
• set :repository, "ssh://[user@]host/~/projectdir.git" # Your clone URL
• set :scm, "git"• set :user, "deployer" # The server's user for deploys• set :scm_passphrase, "p@ssw0rd" # The deploy user's password• Remote Cache/Export
• set :deploy_via, :remote_cache• set :deploy_via, :checkout
• Roles allow you to write capistrano tasks that only apply to certain servers. This really only applies to multi-server deployments. 305
Running Capistrano w/ Bundler• Add the line to config/deploy.rb
• require "bundler/capistrano"• Running cap deploy will now automatically run bundle install
on the remote server with deployment-friendly options. As list of options that can be changed is available in the help for the cap task. To see it, run cap -e bundle:install.
306
Deploy.rb• require 'bundler/capistrano'• set :user, 'username'• set :domain, 'web.host.com'• set :applicationdir, "appdir"• • set :scm, 'git'• set :repository, "ssh://[user@]host/~/projectdir.git"• set :git_enable_submodules, 1 # if you have vendored rails• set :branch, 'master'• set :git_shallow_clone, 1• set :scm_verbose, true • # roles (servers)• role :web, domain• role :app, domain• role :db, domain, :primary => true•
• # deploy config• set :deploy_to, applicationdir• set :deploy_via, :export• • # additional settings• default_run_options[:pty] = true # Forgo errors when deploying from windows• #ssh_options[:keys] = %w(/home/user/.ssh/id_rsa) # If you are using ssh_keysset :chmod755, "app config db lib public vendor script script/* public/disp*"set :use_sudo,
false• • # Passenger• namespace :deploy do• task :start do ; end• task :stop do ; end• task :restart, :roles => :app, :except => { :no_release => true } do• run "#{try_sudo} touch #{File.join(current_path,'tmp','restart.txt')}"• end• end
307
Example: Local Deploy• require 'bundler/capistrano'• set :application, "blog"• set :repository, "/Users/hisham/projects/test/#{application}"• set :scm, :none• set :deploy_to, "/Users/hisham/projects/#{application}"• role :web, "localhost" # Your HTTP server, Apache/etc• role :app, "localhost" • role :db, "localhost", :primary => true # This is where Rails migrations will
run• set :use_sudo, false• set :default_environment, {• 'PATH' => "/usr/local/Cellar/ruby/1.9.2-p180/bin:$PATH"• }• namespace :deploy do• task :restart, :roles => :app, :except => { :no_release => true } do
308
Caching
309
RoR Caching
• Page Caching• Action Caching• Fragment Caching• set config.action_controller.perform_caching = true in
config/environments/*
310
Page Caching
• Allows the request for a generated page to be fulfilled by the webserver.
• Automatically add a .html extension to requests for pages that do not have an extension to make it easy for the webserver to find those pages• Configurable through
config.action_controller.page_cache_extension• By default, the page cache directory is set to Rails.public_path
• Configurable through config.action_controller.page_cache_directory
• Page caches are always stored on disk.311
Products Page Caching
• class ProductsController < ActionController• caches_page :index• def index• @products = Products.all• end• def create• expire_page :action => :index• end• end
312
Page Caching Pros/Cons
• + Almost as fast as static page serving as Rails stack is completely by-passed.
• - Cache expiration is an issue• - Ignores all parameters passed
• /products?page=1 and /products?page=2 will direct to same products.html
• - Cannot be used for private/restricted pages. For example, user custom home page.
• + Cache sweepers can be used to expire cached objects to support a more sophisticated expiration scheme.
313
Action Caching
• Action Caching works like Page Caching except for the fact that the incoming web request does go from the webserver to the Rails stack and Action Pack so that before filters can be run on it before the cache is served.
• This allows authentication and other restriction to be run while still serving the result of the output from a cached copy.
• Action caching uses fragment caching internally• A page that is accessed at hisham.confiz.com/lists/show/1
will result in a fragment named hisham.confiz.com/lists/show/1
• Different representations of the same resource based on format. For example hisham.confiz.com/lists/show/1.xml 314
Products Action Caching
• class ProductsController < ActionController • before_filter :authenticate• caches_action :index• def index• @products = Product.all• end• def create• expire_action :action => :index• end • end
315
Conditional Action Caching
• Use :if (or :unless) to pass a Proc that specifies when the action should be cached
• Use :layout => false to cache without layout so that dynamic information in the layout such as logged in user info or the number of items in the cart can be left uncached
• You can modify the default action cache path by passing a :cache_path option
• If you are using memcached, you can also pass :expires_in• If you pass :layout => false, it will only cache your action
content.
316
Examples
• class ListsController < ApplicationController• before_filter :authenticate, :except => :public• caches_page :public• caches_action :index, :if => proc do |c|• !c.request.format.json? # cache if is not a JSON request• end• caches_action :show, :cache_path => { :project => 1 },
• :expires_in => 1.hour• caches_action :feeds, :cache_path => Proc.new { |c|
c.params }• end 317
Action Caching Pros/Cons
• - Slower than Page Caching• + Allows you to restrict access to cached pages • + Allows you to cache differently based on parameters and
other conditions• + Allows conditional caching• - Allows control over main layout getting cached or not, but not
selective caching of components with the page
318
Fragment Caching
• Fragment Caching allows a fragment of view logic to be wrapped in a cache block and served out of the cache store when the next request comes in.
• #View• <% Order.find_recent.each do |o| %>• <%= o.buyer.name %> bought <% o.product.name %>• <% end %> • <% cache do %>• All available products:• <% Product.all.each do |p| %>• <%= link_to p.name, product_url(p) %>• <% end %>• <% end %>
319
Multiple Fragment Caching
• The cache block in previous example will bind to the action that called it and is written out to the same place as the Action Cache.
• Multiple fragments per action can be supported by • providing action_suffix to the cache call• <% cache(:action => 'recent', :action_suffix => 'all_products')
do %>• All available products:• <% end %>• using globally keyed fragments• <% cache('all_available_products') do %>• All available products:• <% end %>
320
Expiring Fragment Cache
• #controller• expire_fragment(:controller => 'products', :action =>
'recent', :action_suffix => 'all_products')• expire_fragment('all_available_products')• #view• cache({:action => 'recent', :action_suffix =>
'all_products'}, :expires => 5.minutes)• cache('all_available_products', :expires => 5.minutes)
321
SQL Caching
• If Rails encounters the same query again for that request, it will use the cached result set as opposed to running the query against the database again.
• Query caches are created at the start of an action and destroyed at the end of that action and thus persist only for the duration of the action.
322
Cache Stores• Different storing options available for the cached data created by action and fragment
caches.• ActiveSupport::Cache::MemoryStore
• ActionController::Base.cache_store = :memory_store• ActiveSupport::Cache::SynchronizedMemoryStore
• ActionController::Base.cache_store = :synchronized_memory_store• ActiveSupport::Cache::FileStore
• ActionController::Base.cache_store = :file_store, "/path/to/cache/directory"• ActiveSupport::Cache::DRbStore
• ActionController::Base.cache_store = :drb_store, "druby://localhost:9192"• ActiveSupport::Cache::MemCacheStore
• ActionController::Base.cache_store = :mem_cache_store, "localhost"• ActiveSupport::Cache::CompressedMemCacheStore
• ActionController::Base.cache_store = :compressed_mem_cache_store, "localhost"
• config.cache_store can be used in place of ActionController::Base.cache_store in Rails::Initializer.run block ienvironment.rb
323
Low-level Caching
• You can access these cache stores at a low level for storing queries and other objects.
• Rails.cache.read(‘city’) # => nil• Rails.cache.write(‘city’, ‘Lahore’)• Rails.cache.read(‘city’) # => "Lahore"
324
Database Optimizations
325
N+1 Query Problem
• The code below executes 1 ( to find 10 clients ) + 10 ( one per each client to load the address ) = 11 queries in total
• clients = Client.all(:limit => 10) • clients.each do |client|• puts client.address.postcode• end
326
Solution: Eager Loading
• With includes, Active Record ensures that all of the specified associations are loaded using the minimum possible number of queries.
• clients = Client.includes(:address).limit(10) • clients.each do |client|• puts client.address.postcode• end• #SELECT * FROM clients LIMIT 10• #SELECT addresses.* FROM addresses WHERE
(addresses.client_id IN (1,2,3,4,5,6,7,8,9,10))
327
Eager Loading - Multiple Associations• Array of Multiple Associations
• Post.includes(:category, :comments)• Nested Associations
• Category.includes(:posts => [{:comments => :guest}, :tags]).limit(1)• Posts has many comments, comments belong to guests,
posts has many tags
328
Eager Loading - Searching
• ActiveRecords allows you to provide nested conditions that match
• Group.includes(:group_members).where(:name=>'punjabians',:group_members=>{:is_approved => false})
329
Database Indexing
330
Overview
• A database index is a data structure that improves the speed of operations on a database table - Wikipedia
• For every index on a table, there is a penalty both when inserting and updating rows. Indexes also take up space on disk and in memory, which can affect the efficiency of queries.
• Having too many indexes can cause databases to choose between them poorly, actually harming performance rather than improving it.
331
Indexing Simple Associations• Not indexing foreign keys can cripple your app• class User < ActiveRecord::Base• has_many :conversations• end• class Conversation < ActiveRecord::Base• belongs_to :user• end• #add index• add_index :conversations, :user_id
332
Indexing Polymorphic Associations• For polymorphic associations the foreign key is made up of two
columns, one for the id and one for the type• class Artwork < ActiveRecord::Base• has_one :conversation, :as => :subject• end• class Conversation < ActiveRecord::Base• belongs_to :subject, :polymorphic => true• end• #add composite index• add_index :conversations, [:subject_id, :subject_type]
333
Summary• If you have a belongs_to, has_many, has_one, or has_and_belongs_to_many association on a
model), then you almost certainly need to add an index for it.• If you find yourself frequently doing queries on a non-foreign-key column (like user_name or
permalink), you’ll definitely want an index on that column.• If you frequently sort on a column or combination of columns, make sure the index that is
being used for the query includes those sort columns, too (if at all possible). • Many databases (like MySQL, or Postgres prior to 8.1) will only use a single index per table,
per query, so make sure you have indexes defined for the column combinations that you will query on frequently. • A common mistake is to define an index on “user_name” and an index on “account_id”,
and then expect the database to use both indexes to satisfy a query that references both columns. (Some databases will use both indexes, though; be sure and understand how your DBMS uses indexes.)
• Too many indexes can be just as bad as too few, since the DB has to try and determine which of the myriad indexes to use to satisfy a particular query. • Also, indexes consume disk space, and they have to be kept in sync every time an insert,
delete, or update statement is executed. Lots of indexes means lots of overhead, so try to strike a good balance.
• Start with only the indexes you absolutely need, and try to use only those. As problem queries surface, see if they can be rewritten to use existing indexes, and only if they can’t should you go ahead and add indexes to fix them.
334
Amazon S3
335
Amazon S3
• #copied from http://aws.amazon.com/s3/• Amazon S3 is storage for the Internet. • It is designed to make web-scale computing easier for
developers.• Amazon S3 provides a simple web services interface that can
be used to store and retrieve any amount of data, at any time, from anywhere on the web.
• It gives any developer access to the same highly scalable, reliable, secure, fast, inexpensive infrastructure that Amazon uses to run its own global network of web sites.
• The service aims to maximize benefits of scale and to pass those benefits on to developers. 336
AWS Gem
• gem install aws-s3• Allows you to CRUD operations on buckets, and S3Objects.• S3Objects represent the data you store on S3
• S3Object.store('me.jpg', open('headshot.jpg'), 'photos')• S3Object.url_for('beluga_baby.jpg', 'marcel_molina')
• By default authenticated urls expire 5 minutes after they were generated.
337
Paperclip
338
Paperclip
• Paperclip is intended as an easy file attachment library for ActiveRecord.
• The intent behind it was to keep setup as easy as possible and to treat files as much like other attributes as possible.
• This means they aren't saved to their final locations on disk, nor are they deleted if set to nil, until ActiveRecord::Base#save is called.
• It manages validations based on size and presence, if required. • It can transform its assigned image into thumbnails if needed. • Attached files are saved to the filesystem and referenced in the
browser by an easily understandable specification, which has sensible and useful defaults. 339
Usage• Declare that your model has an attachment with the has_attached_file
method, and give it a name. • Paperclip will wrap up up to four attributes (all prefixed with that
attachment's name, so you can have multiple attachments per model if you wish) and give them a friendly front end.
• The attributes are • <attachment>_file_name, • <attachment>_file_size, • <attachment>_content_type, and • <attachment>_updated_at. • Only <attachment>_file_name is required for paperclip to operate.
• Attachments can be validated with Paperclip's validation methods, • validates_attachment_presence, • validates_attachment_content_type, and • validates_attachment_size.
340
Storage
• The files that are assigned as attachments are, by default, placed in the directory specified by the :path option to has_attached_file.
• By default, this location is ":rails_root/public/system/:attachment/:id/:style/:filename"• On standard Capistrano deployments, the public/system
directory is symlinked to the app's shared directory• Files may be stored using Amazon’s S3 Service
341
Setting up Paperclip
• ImageMagick must be installed• Include the gem in your Gemfile:
• gem "paperclip", "~> 2.3"
342
User Avatar Example
• Create file association in model• class User < ActiveRecord::Base• has_attached_file :avatar, :styles => { :medium => "300x300>",
:thumb => "100x100>" }• validates_attachment_presence :avatar• ...• end• Create migration to add field columns
• rails generate migration add_avatar_columns_to_users avatar_file_name:string avatar_content_type:string avatar_file_size:integer avatar_updated_at:datetime
343
User Avatar Example
• Update user form partial:• <% form_for :user, @user, :url => user_path, :html =>
{ :multipart => true } do |form| %>• ....• <%= form.file_field :avatar %>• ....• <% end %>• Update user show view to display avatar• <%= image_tag @user.avatar.url %>• <%= image_tag @user.avatar.url(:medium) %>• <%= image_tag @user.avatar.url(:thumb) %> 344
Using Amazon S3 for Storage• Add to Gemfile• gem ‘aws-s3’• Create config/s3.yml• development:• bucket: bucket-dev• access_key_id: xxx• secret_access_key: xxx• test:• bucket: bucket-test• access_key_id: xxx• secret_access_key: xxx• production:• bucket: bucket-pro• access_key_id: xxx• secret_access_key: xxx• Specify s3 storage and s3 credentials for avatar in User Model• #app/models/user.rb• class User < ActiveRecord::Base• has_attached_file :avatar, :styles => { :medium => "300x300>", :thumb => "100x100>" },• :storage => :s3,• :s3_credentials => "#{RAILS_ROOT}/config/s3.yml",• :path => "/:style/:id/:filename"• ...• end
345
Paperclip and S3
• The default permission used by Paperclip is :public_read• You can set permission on a per style bases by doing the following:• :s3_permissions => {• :original => :private• }• Or globaly:• :s3_permissions => :private• Can use url that expires after sometime to keep privacy
• <%= image_tag @user.avatar.expiring_url %>• <%= image_tag @user.avatar.url(3600, :medium) %>• <%= image_tag @user.avatar.url(3600, :thumb) %>
346
Attaching Videos• class User < ActiveRecord::Base• has_attached_file :video, • :storage => :s3,• :s3_credentials => "#{RAILS_ROOT}/config/s3.yml",• :path => ":attachment/:id/:style/:basename.:extension",• :bucket => 'yourbucketname'
• # Paperclip Validations• validates_attachment_presence :video• validates_attachment_content_type :video, :content_type =>
['application/x-shockwave-flash', 'application/x-shockwave-flash', 'application/flv', 'video/x-flv']
• end• #For detailed example: http://scottmotte.com/archives/181.html
347
Master/Slave Replication
348
Octopus
• Ruby gem that allows an ActiveRecord application (including ActiveRecord 3/Rails 3 applications) to connect to one master and multiple slave databases.
• Most of the documentation centers on its sharding features, but is also very useful for sending writes to the master and reads to the slaves.
349
Octopus Setup
• Add line in Gemfile and run bundle install• gem ‘ar-octopus’, :require => “octopus”
• Create config/shards.yml• If Octopus finds the directive replicated: true, it will assume
all shards as slaves, and the database specified in database.yml as master database.
• In a normal setup, octopus will just use replication for models that are marked as replicated_model. So, if you want to send all writes queries will be sent to master, and all reads queries to slaves, you need to add fully_replicated: true
350
Sample shards.yml• #config/shards.yml• octopus:• replicated: true• fully_replicated: true• environments:• - development• - production• development:• slave1:• adapter: mysql2• encoding: utf8• host: localhost• database: confiz_slave• username: hisham• password: confiz• production:• slave1:• adapter: mysql2• encoding: utf8• host: 123.456.789.10• database: ufone_production• username: hisham• password: confiz
351
Grids
352
WiceGrid=========• Allows the programmer to define the contents of the cell by himself, just like one does
when rendering a collection via a simple table (and this is what differentiates WiceGrid from various scaffolding solutions), but automate implementation of filters, ordering, paginations, CSV export, and so on. Ruby blocks provide an elegant means for this.
• WiceGrid builds the call to the ActiveRecord layer for you and creates a table view with the results of the call including:• paging• sortable columns• filtering by multiple columns• CSV export• saving queries
• Filters are added automatically according to the type of the underlying DB column. • Filtering by more than one column at the same time is possible. • More than one such grid can appear on a page, and manipulations with one grid do not
have any impact on the other.• WiceGrid does not take a collection as an input, it works directly with ActiveRecord
(with the help of will_paginate).353
WiceGrid Setup
• Add to Gemfile and run bundle install• gem 'wice_grid', '3.0.0.pre1'
• Run the generator tasks to copy assets• rails g wice_grid_assets_jquery
354
initialize_grid• Creates a grid object to be used in the view. This is the main model, and the underlying table is the default table - if in other parameters a column name is mentioned without the
name of the table, this table is implied. Just like in an ordinary ActiveRecord find you can use :joins, :include, and :conditions.• The first parameter is an ActiveRecord class name. The generated ActiveRecord call will use it as the receiver of the paginate method: klass.paginate(...)
• The second parameters is a hash of parameters:• :joins - ActiveRecord :joins option.• :include - ActiveRecord :include option.• :conditions - ActiveRecord :conditions option.• :per_page - Number of rows per one page. The default is 10.• :page - The page to show when rendering the grid for the first time. The default is one, naturally.• :order - Name of the column to sort by. Can be of a short form (just the name of the column) if this is a column of the main table (the table of the main ActiveRecord model,
the first parameter of initialize_grid), or a fully qualified name with the name of the table.• :order_direction - :asc for ascending or :desc for descending. The default is :asc.• :name - name of the grid. Only needed if there is a second grid on a page. The name serves as the base name for HTTP parametes, DOM IDs, etc. The shorter the name, the
shorter the GET request is. The name can only contain alphanumeruc characters.• :enable_export_to_csv - <Enable export of the table to CSV. Read the How-To to learn what else is needed to enable CSV export.• :csv_file_name - Name of the exported CSV file. If the parameter is missing, the name of the grid will be used instead.• :custom_order - used for overriding the ORDER BY clause with custom sql code (for example, including a function). The value of the parameter is a hash where keys are fully
qualified names of database columns, and values the required chunks of SQL to use in the ORDER BY clause, either as strings or Proc object evaluating to string. See section ‘Custom Ordering’ in the README.
• :saved_query - id of the saved query or the query object itself to load initially. Read section "Saving Queries How-To" in README for more details.• :after - defined a name of a controller method which would be called by the grid after all user input has been processed, with a single parameter which is a Proc object. Once
called, the object returns a list of all records of the current selection throughout all pages. See section "Integration With The Application" in the README.• :total_entries - If not specified, will_paginate will run a select count• :select - ActiveRecord :select option. Please do not forget that :select is ignored when :include is present. It is unlikely you would need :select with WiceGrid, but if you do, use it
with care :)• :group - ActiveRecord :group option. Use it if you are sure you know what you are doing :)• :with_paginated_resultset - a callback executed from within the plugin to process records of the current page. Can be a lambda object or a controller method name (symbol). The
argument to the callback is the array of the records.• :with_resultset - a callback executed from within the plugin to process all records browsable through all pages with the current filters. Can be a lambda object or a controller
method name (symbol). The argument to the callback is a lambda object which returns the list of records when called. See the README for the explanation.• Defaults for parameters :per_page, :order_direction, :name, and :erb_mode can be changed in lib/wice_grid_config.rb, this is convenient if you want to set a project wide setting
without having to repeat it for every grid instance.355
initialize_grid options contd.
• :enable_export_to_csv - Enable export of the table to CSV. • :csv_file_name - Name of the exported CSV file. If the
parameter is missing, the name of the grid will be used instead.• :total_entries - If not specified, will_paginate will run a select
count• Defaults for parameters :per_page, :order_direction, :name,
and :erb_mode can be changed in lib/wice_grid_config.rb, this is convenient if you want to set a project wide setting without having to repeat it for every grid instance.
356
Controller Method
• Controller• def list• @posts = initialize_grid(Post,• :name => "post",• :page => params[:page],• :include => :tags,• :allow_showing_all_records => false,• :conditions => ["created_at >= ?", Time.now - 10.minutes],• :enable_export_to_csv => true,• :csv_file_name => 'members',• :per_page => 100)• export_grid_if_requested• end
357
View• <%= grid(@tasks_grid) do |g|• g.column :column_name => 'ID', :attribute_name => 'id' do |task|• task.id• end• g.column :column_name => 'Title', :attribute_name => 'title' do |task|• task.title• end• g.column :column_name => 'Description', :attribute_name => 'description' do |task|• task.description• end• g.column :column_name => 'Archived', :attribute_name => 'archived' do |task|• task.archived? ? 'Yes' : 'No'• end• g.column :column_name => 'Added', :attribute_name => 'created_at' do |task|• task.created_at.to_s(:short)• end• g.column do |task|• link_to('Edit', edit_task_path(task))• end• end -%> 358
JQGrid
• Ajax-enabled JavaScript control that provides solutions for representing and manipulating tabular data on the web.
• Can be integrated with any server-side technology, including PHP, ASP, Java Servlets, JSP, ColdFusion, and Perl.
• Download: http://www.trirand.com/blog/?page_id=6
359
Thinking Sphinx
360
Sphinx
• A search engine that indexes data, and then you can query it with search terms to find out which documents are relevant.
• Sphinx is implemented by more than 100 web sites and services, including craigslist.org, scribd.org, mozilla, metacafe, wordpress.org
• Also used in PakWheels.com and Naitazi.com for searching
361
Advantages of Sphinx
• Full Text Searching with performance, relevance (search quality), and integration simplicity in mind!
• Sphinx lets you either batch index and search data stored in an SQL database, or index and search data on the fly.
• A variety of text processing features enable fine-tuning Sphinx for your particular application requirements, and a number of relevance functions ensures you can tweak search quality as well.
• Sphinx clusters scale up to billions of documents and tens of millions search queries per day.
362
Installing Sphinx
• http://sphinxsearch.com/downloads/• ./configure• make• sudo make install
363
Thinking Sphinx
• A Ruby connector between Sphinx and ActiveRecord.• gem 'thinking-sphinx', '2.0.3'
364
Sphinx Components• Sphinx Structure
• A Sphinx daemon (the process known as searchd) can talk to a collection of indexes, and each index can have a collection of sources. Sphinx can be directed to search a specific index, or all of them, but you can’t limit the search to a specific source explicitly.
• Each source tracks a set of documents, and each document is made up of fields and attributes. While in other areas of software you could use those two terms interchangeably, they have distinct meanings in Sphinx (and thus require their own sections in this post).
• Fields• Fields are the content for your search queries – so if you want words tied to a specific
document, you better make sure they’re in a field in your source. They are only string data – you could have numbers and dates and such in your fields, but Sphinx will only treat them as strings, nothing else.
• Attributes• Attributes are used for sorting, filtering and grouping your search results. Their values do
not get paid any attention by Sphinx for search terms, though, and they’re limited to the following data types: integers, floats, datetimes (as Unix timestamps – and thus integers anyway), booleans, and strings. Take note that string attributes are converted to ordinal integers, which is especially useful for sorting, but not much else.
365
Sphinx Components
• Filters• Useful with attributes to limit your searches to certain sets
of results. • for example, limiting a forum post search to entries by a
specific user id. • Sphinx’s filters accept arrays or ranges• The range filters are particularly useful for getting results
from a certain time span.• Relevance
• Relevancy is the default sorting order for Sphinx. • There are a couple of things you can do in your queries to
influence it. 366
Sphinx Indexing
• Everything to set up the indexes for your models goes in the define_index method, within your model.
• class Article < ActiveRecord::Base• # ...• define_index do• indexes subject, :sortable => true• indexes content• indexes author(:name), :as => :author, :sortable => true• has author_id, created_at, updated_at• end• # ...• end
367
Sphinx Fields
• The indexes method adds one (or many) fields, by referencing the model’s column names.
• Use the :as option to signify an alias.• indexes content, :as => :post
• Flag fields as being sortable.• indexes subject, :sortable => true
• Search associated model’s column• indexes author.location, :as => :author_location
• You can also define your indexes as raw SQL• indexes "LOWER(first_name)", :as => :first_name, :sortable
=> true368
Sphinx Attributes
• The has method adds one (or many) attributes, and just like the indexes method, it requires references to the model’s column names.• has author_id• has tags(:id), :as => :tag_ids
369
Conditions and Grouping
• Possible add custom conditions or groupings• define_index do• # ... • where "status = 'active'"• group_by "user_id"• end
370
Running Sphinx
• rake ts:conf • Configures rake using config/sphinx.yml• Creates config/development.sphinx.conf
• rake ts:index• Builds search indexes in db/sphinx/
• rake ts:start• Starts the searchd process that uses
config/development.sphinx.conf and responds to search queries
• rake ts:stop• Stop the searchd process
• rake ts:rebuild• creates stops sphinx, creates configuration, builds indexes
and starts sphinx
371
Basic and Conditional Searching• Basic Searching
• Article.search 'pancakes'• Field Conditions
• Article.search :conditions => {:subject => 'pancakes'}• Article.search 'pancakes', :conditions => {:subject => 'tasty'}
372
Attributes Filters• Filters on attributes can be defined using a similar syntax, but using
the :with option.• Article.search 'pancakes', :with => {:author_id => @pat.id}• #multiple attributes• Article.search 'pancakes', :with => {• :created_at => 1.week.ago..Time.now,• :author_id => @fab_four.collect { |author| author.id }• }• #without• Article.search 'pancakes',• :without => {:user_id => current_user.id}• #conditions and with• Article.search 'pancakes',• :conditions => {:subject => 'tasty'},• :with => {:created_at => 1.week.ago..Time.now}
373
Application Wide Searching/Pagination• Thinking Sphinx allows you to search across all indexed models
• ThinkingSphinx.search 'pancakes'• Can also selectively search across selected models
• ThinkingSphinx.search 'pancakes', :classes => [Article, Comment]
• Supports pagination• Article.search 'pancakes', :page => params[:page], :per_page
=> 20
374
Match Mode• Supports several different ways of matching the given search
keywords• Article.search 'pancakes waffles', :match_mode => :any
• :all• Default, requires a document to have every given word
somewhere in its fields.• :any
• Include at least one of the keywords in their fields.• :phrase
• Matches all given words together in one place, in the same order. Same as wrapping a Google search in quotes.
• :boolean• Allows you to use boolean logic with your keywords. & is AND, | is
OR, and both – and ! function as NOTs. You can group logic within parentheses.
375
Boolean Match Mode
• #ANDs are used implicitly if no logic is given• #search for ‘pancakes & waffles’• Article.search 'pancakes & waffles', :match_mode
=> :boolean• #search for pancakes or waffles• Article.search 'pancakes | waffles', :match_mode => :boolean• #search for pancakes without waffles• Article.search 'pancakes !waffles', :match_mode => :boolean• #search for ‘pancakes topping’ or waffles• Article.search '( pancakes topping ) | waffles',• :match_mode => :boolean 376
Ranking Modes• :proximity_bm25
• The default ranking mode, which combines both phrase proximity and BM25 ranking (see below).
• :bm25• A statistical ranking mode, similar to most other full-text search engines.
• :none• No ranking – every result has a weight of 1.
• :wordcount• Ranks results purely on the number of times the keywords are found in a document.
Field weights are taken into factor.• :proximity
• Ranks documents by raw proximity value.• :match_any
• Returns rankings calculated in the same way as a match mode of :any.• :fieldmask
• Returns rankings as a 32-bit mask with the N-th bit corresponding to the N-th field, numbering from 0. The bit will only be set when any of the keywords match the respective field.
377
Sorting
• By default, Sphinx sorts by how relevant it believes the documents to be to the given search keywords.
• You can also sort by attributes (and fields flagged as sortable), as well as time segments or custom mathematical expressions.
• Direction of sorting is ascending by default• Direction of sorting can be changed using :sort_mode option:• Article.search "pancakes", :order => :created_at,• :sort_mode => :desc
378
Extended/Expression Sorting
• :extended sort_mode allows you to use multiple attributes, or Sphinx’s ranking scores.• Article.search "pancakes", :sort_mode => :extended,• :order => "created_at DESC, @relevance DESC"
• Internal sphinx attributes available for sorting:• @id (The match’s document id)• @weight, @rank or @relevance (The match’s ranking
weight)• @random (Returns results in random order)
• Customize ranking algorithm using :expr sort_mode• Article.search "pancakes", :sort_mode => :expr,• :order => "@weight * views * karma"
379
Field Weights• Sphinx has the ability to weight fields with differing levels of
importance.• Article.search "pancakes", :field_weights => {• :subject => 10,• :tags => 6,• :content => 3• }• Set the weight values in define_index block to apply same custom
weightings to all searches.• set_property :field_weights => {• :subject => 10,• :tags => 6,• :content => 3• }
380
Wildcard Searching
• By default, Sphinx does not pay any attention to wildcard searching using an asterisk character.
• You can turn it on, using enable_star: true in sphinx.yml• Article.search ‘Sana*’• For partial word matching, sphinx supports either index
prefixes (the beginnings of words) or infixes (substrings of words). • You cannot enable both at once, though.
• development:• min_infix_len: 3• # OR• min_prefix_len: 3
381
Word Stemming/Morphology
• Sometimes you may want it to recognise that certain words share pretty much the same meaning.• To enable this kind of behaviour, you need to specify a
morphology (or stemming library) to Sphinx. • development:• morphology: stem_en• #OR• morphology: metaphone• #OR• morphology: soundex• #OR• morphology: stem_en, metaphone
382
Delta Indexing
• In sphinx, you cannot update the fields of a single document in an index.
• Instead, you have to re-process all the data for that index.• Common approach around this issue used by Thinking Sphinx is
to use Delta Indexing
383
Delta Indexing Setup
• Add a delta column to your model• def self.up• add_column :articles, :delta, :boolean, :default => true, :null
=> false• end• Turn on delta indexing for the model• define_index do• # ... • set_property :delta => true• end• Stop, re-index and restart Sphinx.• rake thinking_sphinx:rebuild
384
Delta Indexing/Full Indexing
• The delta index itself will grow to become just as large as the core indexes, and this removes the advantage of keeping it separate.
• It also slows down your requests to your server that make changes to the model records.
• Run full indexing regularly to avoid this.
385
Timestamp/Datetime Deltas
• In Gemfile: • gem 'ts-datetime-delta', '1.0.2':require =>
'thinking_sphinx/deltas/datetime_delta'• In Rakefile:• require 'thinking_sphinx/deltas/datetime_delta/tasks'• In Model:• define_index do• # ...• #uses updated_at by default, change using :delta_column• set_property :delta => :datetime, :threshold => 1.hour• end• Run delta indexing within set threshold:• rake ts:in:delta
386
Delayed Deltas• Queues up the index requests in a separate process (invoked by a constantly
running rake task), instead of dealing with them during each web request.• In Gemfile:• gem 'ts-delayed-delta', '1.1.2',• :require => 'thinking_sphinx/deltas/delayed_delta'• In Rakefile:• require 'thinking_sphinx/deltas/delayed_delta/tasks'• Generate Jobs table used by delayed_job• rails generate delayed_job• In Model:• define_index do• # ... • set_property :delta => :delayed• end 387
Delayed Deltas
• Add boolean column to the model• def self.up• add_column :articles, :delta, :boolean, :default => true,• :null => false• end• Run background task (runs forever):• rake ts:dd
388
Facets
• Facet Searches are search summaries – they provide a breakdown of result counts for each of the defined categories/facets.
• define_index do• # ...• indexes author.name, :as => :author, :facet => true • # ...• has category_id, :facet => true• end• Even if you define your facet as a field, Thinking Sphinx
duplicates it into an attribute, because facets are essentially grouped searches, and grouping can only be done with attributes.
389
Querying Facets• Facets are available through the facets class method on all ActiveRecord
models that have Sphinx indexes, and are returned as a subclass of Hash.• Article.facets # =>• {• :author => {• "Sherlock Holmes" => 3,• "John Watson" => 10• },• :category_id => {• 12 => 4,• 42 => 7,• 47 => 2• }• }
390
Querying Facets
• The facets method accepts the same options as the search method.
• Article.facets 'pancakes'• Article.facets :conditions => {:author => 'John Watson'}• Article.facets :with => {:category_id => 12}• You can also explicitly request just certain facets:• Article.facets :facets => [:author]• You can query fields with facets, as an attribute• Article.search :with => {:author_facet => 'John
Watson'.to_crc32}
391
Global Facets• Faceted searches can be made across all indexed models• ThinkingSphinx.facets 'pancakes'• By default, Thinking Sphinx does not request all possible facets, only those
common to all models and class facet.• ThinkingSphinx.facets 'pancakes' # =>• {• :class => {• 'Article' => 13,• 'User' => 3,• 'Recipe' => 23• }• }• Class facets can be disabled• ThinkingSphinx.facets 'pancakes', :class_facet => false• All facets can be requested• ThinkingSphinx.facets 'pancakes', :all_facets => true
392
Displaying Facets
• <% @facets.each do |facet, facet_options| %>• <h5><%= facet %></h5>• <ul>• <% facet_options.each do |option, count| %>• <li><%= link_to "#{option} (#{count})",• :params => {facet => option, :page => 1} %></li>• <% end %>• </ul>• <% end %>
393
Thinking Sphinx Deployment
• Include the recipe in order to define the necessary tasks for us:• # If you're using Thinking Sphinx as a gem:• require 'thinking_sphinx/deploy/capistrano'• Define callbacks in order to make sure that Sphinx is properly
configured, indexed, and started on each deploy• task :before_update_code, :roles => [:app] do• thinking_sphinx.stop• end• task :after_update_code, :roles => [:app] do• symlink_sphinx_indexes• thinking_sphinx.configure• thinking_sphinx.start• end
394
Messaging QueuesReference: http://www.slideshare.net/skyfallsin/message-queues-in-ruby-an-overview
395
Background
• A request is supposed to return a response really fast.• However, there are certain actions that may take longer than a
few milliseconds• Examples:
• Delivering e-mail• Image processing (resizing, cropping etc.)• Sending out notifications• Talking to other web services
• These actions, if not extracted out to run asynchronously, will slow down the application
• Result in bad user experience.396
Messaging Queues?
• A server that you submit asynchronous ‘jobs’ to.• Interfaces between your Rails app and the ‘workers’ that
perform each ‘job’.• ‘Workers’ pull jobs from each queue and process them ASAP.• Queues need to be FAST and RELIABLE.
397
Delayed Job
• Backed by database• Serializes Ruby Job classes (YAML) into a ‘jobs’ table• Easy to use syntax• # without delayed_job• @user.activate!(@device)• # with delayed_job• @user.delay.activate!(@device)
398
Handle Asynchronously
• If a method should always be run in the background, you can call handle_asynchronously after the method declaration
• Can also set priority for the asynchronous method• handle_asynchronously :send_mailer, :priority => 20
• Set time for running (delaying)• handle_asynchronously :in_the_future, :run_at => Proc.new
{ 5.minutes.from_now }
399
Setup Delayed Job
• Add line to Gemfile,• gem ‘delayed_job’
• Run ‘bundle install’• Generate migration and delayed_job script
• rails generate delayed_job• Run db migration to create the jobs table
• rake db:migrate
400
Running Background Process• Workers can run on any computer as long as they have access
to database! (and clock is in sync)• Start two workers in the background
• RAILS_ENV=production script/delayed_job -n 2 start• Run the job in the foreground
• rake jobs:work
401
Delayed Job Summary
• Will get you 80% of the way there, has some nice advanced features (priority, retries etc.)
• Should be careful not to overload your jobs table• Backed up jobs can lead to query slowdown on your site• Monitor and profile workers before you launch on production
(and monitor their running through automated tools such as monit)
• Delayed Job also supports other backends for storing jobs besides Active Record.
402
Starling
• Extracted out from early version of Twitter.• Speaks memcached protocol• Easy to use syntax• QUEUE = Starling.new(‘127.0.0.1:22122’)• QUEUE.set(‘my_queue’, queue_object)• QUEUE.get(‘my_queue’)
403
Setup Starling
• Add line to Gemfile• gem ‘starling’
• Create user to run starling server• sudo /usr/sbin/adduser -s /sbin/nologin starling
• Create directory to store starling queues and set ownership• sudo mkdir -p /var/spool/starling• sudo chown starling:starling /var/spool/starling
• Create directory to store starling process id and set ownership• sudo mkdir -p /var/run/starling• sudo chown starling:starling /var/run/starling
• Start starling server as starling user• sudo -u starling starling -P /var/run/starling/starling.pid -d
404
Running background Proces• Have to create your own background process• class MessageDaemon• def initialize(starling_queue, handler)• @starling_queue, @handler = starling_queue, handler• end• def start_listening• begin• starling = Starling.new(‘127.0.0.1:22122’)• while object = starling.get(@starling_queue)• @handler.handle(object)• end• rescue MemCache::MemCacheError, IndexError => e• ...• rescue StandardError => e• ...• end• end
405
Starling Summary• Will have to poll the queue to get new jobs.
• By default, keeps on checking the server every 0.1 ms to check if something is available for the queue.
• Use ‘fetch’ instead of ‘get’ method which if you want to implement your own polling mechanism
• At least 10x faster than a queue based over database.• Very light-weight and adaptable to different needs.• Monitor and profile workers before you launch on production (and
monitor their running through automated tools such as monit)• Does not support interoperability.
• Both publisher and subscriber are expected to be written in Ruby.• Does have performance benefits as a result though by saving cost
on marshalling and unmarshalling to and from a language-neutral format (such as json or xml).
406
Resque
• Sits on top of Redis• Redis?
• superfast data structures store like memcached, but smarter in-memory for the most part persisted to disk asynchronously
• sets, lists & corresponding operations• Persists jobs to Redis as JSON objects• Background jobs can be any Ruby class or module that responds
to perform. • Resque is heavily inspired by DelayedJob (which rocks) and comes
with:• A Ruby library for creating, querying, and processing jobs• A Rake task for starting a worker which processes jobs• A Sinatra app for monitoring queues, jobs, and workers.
407
Setting up Resque• Install redis
• wget http://redis.googlecode.com/files/redis-2.2.12.tar.gz• tar xzf redis-2.2.12.tar.gz• cd redis-2.2.12• make• sudo make install• cp redis.conf /etc/
• redis-server /etc/redis.conf• Add line to Gemfile
• gem 'resque', :require => "resque/server"• Create lib/tasks/resque.rake and add line
• require 'resque/tasks'• Listen to all queues
• QUEUE='*' rake resque:work • COUNT=5 QUEUE=* rake resque:workers #running multiple workers
408
Resque Jobs• Resque jobs are Ruby classes (or modules) which respond to the perform method.• The @queue class instance variable determines which queue Archive jobs will be placed
in.• class Archive• @queue = :file_serve• def self.perform(repo_id, branch = 'master')• repo = Repository.find(repo_id)• repo.create_archive(branch)• end• end• To place an Archive job on the file_serve queue, we might add this to our application's
pre-existing Repository class:• class Repository• def async_create_archive(branch)• Resque.enqueue(Archive, self.id, branch)• end• end
409
Resque Persistence
• Jobs are persisted to queues as JSON objects. • your jobs must only accept arguments that can be JSON
encoded.• {• 'class': 'Archive',• 'args': [ 44, 'masterbrew' ]• }
410
Resque Frontend
• Provides statistics to monitor resque
411
Resque Summary
• Does not support Ruby objects that cannot be JSON encoded.• Supports potentially longer queues.• Easy to define new jobs than starling• Monitor and profile workers before you launch on production
(and monitor their running through automated tools such as monit)
412
AJAX Contd.
413
Autocomplete Tags
• Create TagsController query function that accepts params[:q] and return each result with new line
• Set :id => “tag_name” for name field in app/views/tags/_form.html.erb
• Set tags.js to include autocomplete binding• $(document).ready(function(){• $("#tag_name").autocomplete({• source: '/tags',• minLength: 2• });• });
414
JQuery UI DatePicker
• $('#date').datepicker();• Download jquery-ui css in public/stylesheets/ for a cleaner
look!• wget
http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.14/themes/base/jquery-ui.css
415
Periodically Call Remote Function• Periodically call remote function• $(document).ready(• function(){• setInterval(function(){• $('#mydiv').load('/controller/action');• }, 3000);• });
416
Sortable List=============• Unobstrusively change the click's behavior via javascript.• <!-- in your view -->• <a id="foo" href="http://foo.info">This is foo!</a>
• //in application.js (possibly)• $(document).ready(function(){• $('#foo').click(function(){ • //handle the click• return false; //cancel the browser's traditional event.• });• });• Adjust layout to take in javascript:• <html>• <head><title>My boring blog</title>• <%= javascript_tag "var AUTH_TOKEN = #{form_authenticity_token.inspect};" if protect_against_forgery? %>• </head>• <body>• ...• yield :javascript• </body>• </html>
417
Sortable List• #public/javascripts/application.js• // This sets up the proper header for rails to understand the request type,• // and therefore properly respond to js requests (via respond_to block, for example)• $.ajaxSetup({ • 'beforeSend': function(xhr) {xhr.setRequestHeader("Accept", "text/javascript")}• })
• $(document).ready(function() {• // UJS authenticity token fix: add the authenticy_token parameter• // expected by any Rails POST request.• $(document).ajaxSend(function(event, request, settings) {• // do nothing if this is a GET request. Rails doesn't need• // the authenticity token, and IE converts the request method• // to POST, just because - with love from redmond.• if (settings.type == 'GET') return;• if (typeof(AUTH_TOKEN) == "undefined") return;• settings.data = settings.data || "";• settings.data += (settings.data ? "&" : "") + "authenticity_token=" +
encodeURIComponent(AUTH_TOKEN);• });
418
Model Setup
• Install acts_as_list• Add line to Gemfile and run bundle install
• gem ‘acts_as_list’• rails generate migration add_position_to_tasks • bundle install rake db:migrate• Add acts_as_list :scope => :user_story to the Task model.• Optionally, add default_scope => 'position' to the Task model.
419
View Setup
• <ul id="tasks-list">• <% @user_story.tasks.each do |t| %>• <li id="task_<%= t.id -%>">• <span class="name">• <%= t.name -%>• </span>• <span class="handle">[handle]<span>• </li>• <% end %>• <ul>
420
Unobtrusive Javascript Setup• <% content_for :javascript do %>
• <% javascript_tag do %>• $('#tasks-list').sortable(• {• axis: 'y', • dropOnEmpty:false, • handle: '.handle', • cursor: 'crosshair',• items: 'li',• opacity: 0.4,• scroll: true,• update: function(){• $.ajax({• type: 'post', • data: $('#tasks-list').sortable('serialize') + '&id=<%=@user_story.id-%>', • dataType: 'script', • complete: function(request){• $('#tasks-list').effect('highlight');• },• url: '/user_stories/prioritize_tasks'})• }• })• <% end %>• • <% end %> 421
Controller/Route Setup• def prioritize_tasks• user_story = UserStory.find(params[:id])• tasks = user_story.tasks• tasks.each do |task|• task.position = params['task'].index(task.id.to_s) + 1• task.save• end• render :nothing => true• end• #Routes Setup• # add the :collection option• map.resources :user_story, :collection => { :prioritize_tasks => :post } 422
Security Contd.
423
reCAPTCHA
424
reCAPTCHA
• #http://www.google.com/recaptcha/whyrecaptcha• Helps prevent automated abuse of your site (such as comment
spam or bogus registrations) by using a CAPTCHA to ensure that only humans perform certain actions.
• Has an audio test that allows blind people to freely navigate your site.
• Over 100,000 sites use reCAPTCHA, including household names like Facebook, Ticketmaster, and Craigslist.
425
reCAPTCHA Setup
• Add line to Gemfile• gem "ambethia-recaptcha", :lib => "recaptcha/rails", :source => "
http://gems.github.com"• Get a reCAPTCHA account at http://www.google.com/recaptcha.• Setup your API Keys• Recaptcha.configure do |config|• config.public_key =
'6Lc6BAAAAAAAAChqRbQZcn_yyyyyyyyyyyyyyyyy'• config.private_key =
'6Lc6BAAAAAAAAKN3DRm6VA_xxxxxxxxxxxxxxxxx'• end• Use recaptcha_tags to output the necessary HTML code and then
verify the input with verify_recaptcha.426
Using reCAPTCHA• In View:• <%= recaptcha_tags %>• #If you are using SSL, use this instead:• #<%= recaptcha_tags :ssl => true %>• In Controller:• if verify_recaptcha• @thing.save!• redirect_to success_path• else• flash[:error] = "There was an error with the recaptcha code below. Please re-enter the code and click submit." • render :action => 'new'• end• #OR• if verify_recaptcha(:model => @thing, :message => 'There was an error with the recaptcha code below. Please re-
enter the code and click submit.') && @thing.save• redirect_to success_path• else• render :action => 'new'• end
427
Sessions Hijacking• Sniff the cookie in an insecure network.
• A wireless LAN can be an example of such a network. • In an unencrypted wireless LAN it is especially easy to listen to the traffic of
all connected clients. • This is one more reason not to work from a coffee shop. • For the web application builder this means to provide a secure connection
over SSL.• Most people don’t clear out the cookies after working at a public terminal.
• So if the last user didn’t log out of a web application, you would be able to use it as this user.
• Provide the user with a log-out button in the web application, and make it prominent.
• Many cross-site scripting (XSS) exploits aim at obtaining the user’s cookie. • Instead of stealing a cookie unknown to the attacker, they fix a user’s session
identifier (in the cookie) known to them. 428
Session Guidelines
• Critical data should not be stored in session. If the user clears his cookies or closes the browser, they will be lost. • And with a client-side session storage, the user can read the
data.• If using CookieStore for session storage, then the security of
this storage depends on the secret set (and on the digest algorithm, which defaults to SHA512, which has not been compromised, yet). • So don’t use a trivial secret, i.e. a word from a dictionary, or
one which is shorter than 30 characters. Put the secret in your environment.rb
• Already in-place for Rails 3 in: config/initializers/secret_token.rb
429
Mass Assignment
• Without any precautions Model.new(params[:model]) allows attackers to set any database column’s value.
• def signup• params[:user] # => {:name => “ow3ned”, :admin => true}• @user = User.new(params[:user])• end• Use attr_protected to define attributes that will not be
accessible for mass-assignment• Even better is to use attr_accessible to define attributes that
will be accessible for mass-assignment
430
SQL Injection• SQL injection attacks aim at influencing database queries by manipulating
web application parameters. • A popular goal of SQL injection attacks is to bypass authorization.
• User.find(:first, "login = '#{params[:name]}' AND password = '#{params[:password]}'")
• params[:name] => ’ OR ‘1’=‘1• params[:password] => ’ OR ’2’>’1• This will simply find the first record in the database, and grants access
to this user.• Another goal is to carry out data manipulation or reading arbitrary data.
• Project.find(:all, :conditions => "name = '#{params[:name]}'")• params[:name] => ’ OR 1 --’• The two dashes start a comment ignoring everything after it. So the
query returns all records from the projects table including those blind to the user.
431
SQL Injection Solution?
• Instead of passing a string to the conditions option, you can pass an array to sanitize tainted strings• Model.find(:first, :conditions => ["login = ? AND password
= ?", entered_user_name, entered_password])• Model.find(:first, :conditions => {login: entered_user_name,
password: entered_password})
432
Cross-site Request Forgery (CSRF)• Add this to the header of site-wide layout to prevent cross-site
request forgery• <%= csrf_meta_tag %>
• Required for UJS helpers to work.• No need to get into the details, but just know that Rails is
working hard to make your application secure.
433
Testing Contd.
434
Model Testing - Name• #spec/models/user_spec.rb• require 'spec_helper'• describe User do• ...• it "should require a name" do• no_name_user = User.new(@attr.merge(:name => ""))• #RSpec adopts the useful convention of allowing us to test any boolean method
by dropping the question mark and prepending be_• no_name_user.should_not be_valid• end• it "should reject names that are too long" do• long_name = "a" * 51• long_name_user = User.new(@attr.merge(:name => long_name))• long_name_user.should_not be_valid• end• end
435
Model Testing - Email Address• #spec/models/user_spec.rb• require 'spec_helper'• describe User do• ...• it "should require an email address" do• no_email_user = User.new(@attr.merge(email: ""))• no_email_user.should_not be_valid• end
• it "should accept valid email addresses" do• addresses = %w[[email protected] [email protected] [email protected]]• addresses.each do |address|• valid_email_user = User.new(@attr.merge(email: address))• valid_email_user.should be_valid• end• end
• it "should reject invalid email addresses" do• addresses = %w[user@foo,com user_at_foo.org example.user@foo.]• addresses.each do |address|• invalid_email_user = User.new(@attr.merge(email: address))• invalid_email_user.should_not be_valid• end• end
• it "should reject duplicate email addresses" do• # Put a user with given email address into the database.• User.create!(@attr)• user_with_duplicate_email = User.new(@attr)• user_with_duplicate_email.should_not be_valid• end• end
436
Model Testing - Password Validations• #spec/models/user_spec.rb• require 'spec_helper'• describe User do• ...• describe "password validations" do
• it "should require a password" do• User.new(@attr.merge(password: "", password_confirmation: "")).• should_not be_valid• end
• it "should require a matching password confirmation" do• User.new(@attr.merge(:password_confirmation => "invalid")).• should_not be_valid• end
• it "should reject short passwords" do• short = "a" * 5• hash = @attr.merge(password: short, :password_confirmation => short)• User.new(hash).should_not be_valid• end
• it "should reject long passwords" do• long = "a" * 41• hash = @attr.merge(password: long, :password_confirmation => long)• User.new(hash).should_not be_valid• end• end• end
437
Code for Model Testing• $ rails generate model User name:string email:string• #app/models/user.rb• class User < ActiveRecord::Base• attr_accessor :password• attr_accessible :name, :email, :password, :password_confirmation• email_regex = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
• validates :name, presence: true,• length: { maximum: 50 }• validates :email, presence: true,• format: { with: email_regex },• uniqueness: true• validates :password, presence: true,• confirmation: true,• length: { within: 6..40 }• end
438
Factory Girl
• A fixtures replacement with a straightforward definition syntax, • support for multiple build strategies (saved instances,
unsaved instances, attribute hashes, and stubbed objects), and
• support for multiple factories for the same class (user, admin_user, and so on), including factory inheritance.
• Creates entries into the database as required.• Add to Gemfile and run ‘bundle install’• group :test do• ...• gem 'factory_girl_rails'• end
439
Factory Girl Usage• #spec/factories.rb• Factory.define :user do |f|• f.sequence(:username) { |n| "foo#{n}" }• f.password "foobar"• f.password_confirmation { |u| u.password }• f.sequence(:email) { |n| "foo#{n}@confiz.com" }• end
• Factory.define :article do |f|• f.name "Foo"• f.association :user• end
• #spec/models/user_spec.rb• describe User do• it "should authenticate with matching username and password" do• user = Factory(:user, username: 'frank', password: 'secret')• User.authenticate('frank', 'secret').should == user• end• • it "should not authenticate with incorrect password" do• user = Factory(:user, username: 'frank', password: 'secret')• User.authenticate('frank', 'incorrect').should be_nil• end• end
440
User Show Action Testing• #spec/controllers/users_controller_spec.rb• require 'spec_helper'
• describe UsersController do• render_views
• describe "GET 'show'" do• before(:each) do• @user = Factory(:user)• end• • it "should be successful" do• get :show, :id => @user• response.should be_success• end
• it "should find the right user" do• get :show, :id => @user• #assigns method takes in a symbol argument and returns the value of the corresponding instance variable in the controller action• assigns(:user).should == @user• end• end• ...• end 441
Controller Testing for Failed User Sign-up• Should not create a user
• count of users should not increase• Should keep the user on the sign-up page
• Title should be ‘Sign Up’• Should re-render the new user page
• new user template should be rendered
442
Controller Testing for Failed User Signup• #spec/controllers/users_controller_spec.rb• require 'spec_helper'• describe UsersController do• render_views• ...• describe "POST 'create'" do• describe "failure" do• before(:each) do• @attr = { :name => "", :email => "", password: "",• :password_confirmation => "" }• end
• it "should not create a user" do• lambda do• post :create, :user => @attr• end.should_not change(User, :count)• end
• it "should have the right title" do• post :create, :user => @attr• response.should have_selector("title", :content => "Sign up")• end
• it "should render the 'new' page" do• post :create, :user => @attr• response.should render_template('new')• end• end• end• end
443
Controller Testing for Successful User Sign-up• Should create a user
• Count of users should increase by 1• Should be take to user’s show page
• redirect to user show page for that user
444
Controller Testing for Successful User Sign-up• require 'spec_helper'
• describe UsersController do• render_views• ...• describe "POST 'create'" do• ...• describe "success" do• before(:each) do• @attr = { :name => "New User", :email => "[email protected]",• password: "foobar", :password_confirmation => "foobar" }• end
• it "should create a user" do• lambda do• post :create, :user => @attr• end.should change(User, :count).by(1)• end
• it "should redirect to the user show page" do• post :create, :user => @attr• response.should redirect_to(user_path(assigns(:user)))• end • end• end• end 445
Integration Tests w/ Navigation!• RSpec integration tests also support a highly expressive web-
navigation syntax.• Especially useful for simulating filling out forms
• visit signin_path• fill_in "Name", :with => "Hisham Malik"
• The first arguments to fill_in are the label values, i.e., exactly the text the user sees in the browser
• click_button
446
Integration Testing for Failed User Sign-up• When you leave the name, email, password, and confirmation
fields blank• Click Submit button• User should be taken back to the new user page.• The response should have error_explanation div
• <div id="error_explanation">...</div>• User count is also not changed
447
Integration Testing for Failed User Sign-up• $ rails generate integration_test users• require 'spec_helper'
• describe "Users" do• describe "signup" do• describe "failure" do• it "should not make a new user" do• lambda do• visit signup_path• fill_in "Name", :with => ""• fill_in "Email", :with => ""• fill_in "Password", :with => ""• fill_in "Confirmation", :with => ""• click_button• response.should render_template('users/new')• response.should have_selector("div#error_explanation")• end.should_not change(User, :count)• end• end• end• end
448
Active Resource
449
Active Resource
• Represents your RESTful resources as manipulatable Ruby objects.
• class Post < ActiveResource::Base• self.site = “http://localhost:3000”• self.format = :json #by default format is xml• end• post = Post.find(1)• post.inspect• post.body = “new body”• post.save
450
Custom REST Methods• Active Resource also supports defining your own custom REST methods• # POST to the custom 'register' REST method, i.e. POST
/people/new/register.xml.• Person.new(:name => 'Ryan').post(:register)
• # PUT an update by invoking the 'promote' REST method, i.e. PUT /people/1/promote.xml?position=Manager.
• Person.find(1).put(:promote, :position => 'Manager')
• # GET all the positions available, i.e. GET /people/positions.xml.• Person.get(:positions)
• # DELETE to 'fire' a person, i.e. DELETE /people/1/fire.xml.• Person.find(1).delete(:fire)
451
Authentication• Supports HTTP Basic Authentication• class Person < ActiveResource::Base• self.site = "http://localhost:3000/”• self.user = "hisham"• self.password = "confiz"• end• Supports Certificate Authentication• class Person < ActiveResource::Base• self.site = "https://secure.api.people.com/"• self.ssl_options = {:cert =>
OpenSSL::X509::Certificate.new(File.open(pem_file))• :key => OpenSSL::PKey::RSA.new(File.open(pem_file)),• :ca_path => "/path/to/OpenSSL/formatted/CA_Certs",• :verify_mode => OpenSSL::SSL::VERIFY_PEER}• end
452
Referenceshttp://marklunds.com/s5/rails101/html/rails_introduction.htmlhttp://www.troubleshooters.com/codecorn/ruby/symbols.htmhttp://www.techotopia.com/index.php/Ruby_Essentialshttp://www.slideshare.net/vishnu/the-ruby-programming-language-or-why-are-you-wasting-brain-powerhttp://guides.rubyonrails.org/association_basics.htmlhttp://guides.rubyonrails.org/layouts_and_rendering.htmlhttp://guides.rubyonrails.org/routing.htmlhttp://guides.rubyonrails.org/active_record_validations_callbacks.htmlhttp://guides.rubyonrails.org/getting_started.htmlhttp://blog.bernatfarrero.com/jquery-and-rails-3-mini-tutorial/http://net.tutsplus.com/tutorials/javascript-ajax/using-unobtrusive-javascript-and-ajax-with-rails-3/http://www.simonecarletti.com/blog/2010/06/unobtrusive-javascript-in-rails-3/http://www.aaginskiy.com/technology/2011/02/deploying-rails-3-apps-with-capistrano/http://freelancing-god.github.com/ts/enhttp://www.slideshare.net/skyfallsin/message-queues-in-ruby-an-overviewhttps://github.com/defunkt/resqueRails 3 in a Nutshell: http://ofps.oreilly.com/titles/9780596521424/index.htmlhttp://railscasts.com/episodes/158-factories-not-fixtureshttp://leikind.org/pages/wicegridhttp://awesomeful.net/posts/47-sortable-lists-with-jquery-in-railshttp://tomafro.net/2009/08/using-indexes-in-rails-index-your-associationshttp://weblog.jamisbuck.org/2006/10/23/indexing-for-db-performancehttp://www.simonecarletti.com/blog/2010/06/unobtrusive-javascript-technique/
453