Rapid Application Development using Ruby on Rails

  • 1,172 views
Uploaded on

Rapid Application Development using Ruby on Rails

Rapid Application Development using Ruby on Rails

More in: Technology
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Be the first to comment
No Downloads

Views

Total Views
1,172
On Slideshare
0
From Embeds
0
Number of Embeds
3

Actions

Shares
Downloads
42
Comments
0
Likes
2

Embeds 0

No embeds

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
    No notes for slide

Transcript

  • 1. Rapid Application Development using Ruby on Rails Presented By: Hisham Malik http://www.arkhitech.com 1
  • 2. 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
  • 3. Day 2 - Details • DB Migrations • MVC – ActiveRecord, ActionController, ActionView • Form Helpers • Routing 3
  • 4. Day 3 - Optimizations • Testing • Ruby Language – More Details. • Active Records - More Details. • Live Demo Contd. • AJAX – Unobtrusive Javascripting (UJS) • Security 4
  • 5. 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
  • 6. Day 1 6
  • 7. 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
  • 8. 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
  • 9. 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
  • 10. Environment Check! • Ruby - 2.0.0 • ruby -v • Rails - 4.0.0 • rails -v 10
  • 11. 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
  • 12. Getting Up and Running Quickly - Demo 12
  • 13. Creating Sample Application • rails new sample • You can see all of the switches that the Rails application builder accepts by running rails -h. 13
  • 14. 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
  • 15. Generated User Pages/URL 15
  • 16. Ruby “I always thought Smalltalk would beat Java. I just didn’t know it would be called ‘Ruby’ when it did”- Kent Beck 16
  • 17. Ruby Principles • Focusing on the human • Dynamic Programming • Principle of Least Surprise • Principle of Succinctness • Lightweight Language • Choosing good names • Embedding hidden messages 17
  • 18. 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
  • 19. Principle of Least Surprise • diff = ary1 – ary2 • union = ary1 + ary2 19
  • 20. 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
  • 21. 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
  • 22. 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
  • 23. 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
  • 24. 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
  • 25. 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
  • 26. 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
  • 27. 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
  • 28. 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
  • 29. 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
  • 30. attr_reader • class Person • attr_reader :name • def initialize(name) • @name = name • end • end • person = Person.new("Andreas") • puts person.name 30
  • 31. 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
  • 32. 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
  • 33. Class Instance Methods • andreas = Person.new(“Andreas”) • def andreas.andreas_says_hi • “Andreas says hi” • end • andreas.andreas_says_hi 33
  • 34. 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
  • 35. 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
  • 36. 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
  • 37. 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
  • 38. 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
  • 39. 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
  • 40. 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
  • 41. 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
  • 42. 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
  • 43. Easier Conditional Statements • puts “Good Morning” if Time.now.hour < 12 • puts “Good Night” unless Time.now.hour < 20 43
  • 44. Iterators: while, until, and for. Keywords: break and 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
  • 45. 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
  • 46. Regular Expressions • puts 'matches' if ‘Ruby’ =~ /^(ruby|python)$/i • “GonRuby” =~ /Gos+(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
  • 47. 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
  • 48. Rails MVC 48
  • 49. 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
  • 50. 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
  • 51. 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
  • 52. Rails - MVC 52
  • 53. 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
  • 54. Creating Blog Project 54
  • 55. 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
  • 56. 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:create 56
  • 57. 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
  • 58. Hello Rails • rails server 58
  • 59. 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 Rakefile You can put your own tasks under lib/tasks bundle exec rake -T bundle exec rake -T db #See a list of database tasks 59
  • 60. 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
  • 61. 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
  • 62. Live Demo 62
  • 63. 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
  • 64. 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
  • 65. Creating Post Resource • rails generate scaffold Post name:string title:string content:text • bundle exec rake db:migrate 65
  • 66. 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
  • 67. Under The Hood 67
  • 68. 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
  • 69. Posts Model • #app/models/post.rb • class Post < ActiveRecord::Base • end 69
  • 70. 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
  • 71. Post Controller app/controller/posts_controller.rb 71
  • 72. 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
  • 73. 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
  • 74. 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
  • 75. 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
  • 76. Comments can be given for Post? 76
  • 77. 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
  • 78. 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
  • 79. Comments Controller? 79
  • 80. 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
  • 81. Add Comments to Post • Comments should be added where the post is shown. 81
  • 82. 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
  • 83. 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
  • 84. 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
  • 85. Day 2 85
  • 86. 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
  • 87. Migrations A way to evolve your database schema over time Migrations use a database independent Ruby API Migration classes extend ActiveRecord::Migration and have an up and a down method Migrations are stored in files in db/migrate, one for each migration class. Running migrations: bundle exec rake db:migrate 87
  • 88. 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
  • 89. 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
  • 90. 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
  • 91. 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
  • 92. 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
  • 93. 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
  • 94. 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
  • 95. 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 functio nally equival ent
  • 96. 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
  • 97. 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
  • 98. 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
  • 99. Boolean Attributes Everything except nil and false is true in Ruby However, in MySQL boolean columns are char(1) with values 0 or 1, both of which are true in Ruby. Instead of saying user.admin, say user.admin? When you add the question mark, false is the number 0, one of the strings ‘0’, ‘f ’, ‘false’, or ‘’, or the constant false 99
  • 100. Active Record 100
  • 101. Fundamentals One database table maps to one Ruby class Ruby classes live under app/models and extend ActiveRecord::Base Table names are plural and class names are singular Database columns map to attributes, i.e. get and set methods, in the model class All tables have an integer primary key called id Database tables are created with migrations 101
  • 102. Methods where select group order limit offset joins includes lock readonly from having 102
  • 103. 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
  • 104. 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
  • 105. 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
  • 106. 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
  • 107. 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
  • 108. 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
  • 109. 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
  • 110. 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
  • 111. 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
  • 112. 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
  • 113. 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
  • 114. 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
  • 115. 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
  • 116. 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
  • 117. 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
  • 118. 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
  • 119. Passing Variables to Partials • 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
  • 120. Partials and Collections • <% 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
  • 121. 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
  • 122. 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
  • 123. 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
  • 124. 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
  • 125. Selecting Layout at Runtime • class BlogController < ActionController::Base • layout :determine_layout • private • def determine_layout • user.admin? ? “admin” : “standard” • end • end 125
  • 126. Yield/Content_for • Layout: • <html> • <head> • <%= yield :head %> • </head> • <body> • <%= yield %> • </body> • </html> 126
  • 127. Yield/Content_for/Provide • <%= content_for :head do %> • <title>A simple page</title> • <% end %> • <%= provide :head %> • <p>Hello, Rails!</p> 127
  • 128. 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
  • 129. 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
  • 130. AssetTagHelper • Provide methods for generating HTML that links views to feeds, JavaScript, stylesheets, images, videos and audios. • http://api.rubyonrails.org/classes/ActionView/Helpers/AssetTagH elper.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
  • 131. 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
  • 132. 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
  • 133. 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
  • 134. 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
  • 135. TextHelper • Provides common-use text manipulation methods • http://api.rubyonrails.org/classes/ActionView/Helpers/TextHelper.ht ml • 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 david@loudthinking.com") • <tr class="<%= cycle("even", "odd") -%>"> 135
  • 136. UrlHelper • Provides an easy way to create dynamic urls based on • http://api.rubyonrails.org/classes/ActionView/Helpers/UrlHelper.ht ml • 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
  • 137. UrlHelper • button_to "New", :action => "new" • <form method="post" action="/controller/new" class="button_to"> • <div><input value="New" type="submit" /></div> • </form>" 137
  • 138. Protect Email Addresses • Encoding emails makes it more difficult for web spiders to lift email addresses off of a site • mail_to "me@domain.com", "My email", :encode => "javascript" • mail_to "me@domain.com", "My email", :encode => "hex" 138
  • 139. 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
  • 140. 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
  • 141. *_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
  • 142. 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
  • 143. 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
  • 144. 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
  • 145. 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
  • 146. 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
  • 147. 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
  • 148. 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
  • 149. Routing 149
  • 150. Routing Options • Resource Routing • Non-Resourceful Routes 150
  • 151. 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
  • 152. 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
  • 153. 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
  • 154. 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
  • 155. 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
  • 156. 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
  • 157. 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
  • 158. 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
  • 159. 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
  • 160. 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
  • 161. 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
  • 162. 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://examp le.com/resour ces/ 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 Delete the entire collection.
  • 163. 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
  • 164. 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
  • 165. 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
  • 166. Empty Route • The root of the web site is the empty route. • root :to => 'welcome#show' 166
  • 167. 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
  • 168. Day 3 168
  • 169. Testing 169
  • 170. 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
  • 171. 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
  • 172. 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
  • 173. 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
  • 174. 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
  • 175. 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
  • 176. 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
  • 177. 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
  • 178. Do it yourself? 178
  • 179. 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 179
  • 180. 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
  • 181. 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
  • 182. 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
  • 183. 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
  • 184. 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
  • 185. 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
  • 186. 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
  • 187. User Model Testing • User should be created successfully when passed valid attributes 187
  • 188. User Model Testing • #spec/models/user_spec.rb • require 'spec_helper' • describe User do • before(:each) do • @attr = , name: "Hisham Malik", email: “hisham@arkhitech.com”, password: "arkhitech", password_confirmation: "confiz" } • end • it "should create a new instance given valid attributes" do • User.create!(@attr) • end • end 188
  • 189. Have a test to write, but not ready yet(requirements missing)? 189
  • 190. 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
  • 191. Ruby - Advance Topics 191
  • 192. 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
  • 193. 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
  • 194. 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
  • 195. Reflection • method_missing • const_missing • instance_variable_get • instance_variable_set 195
  • 196. 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
  • 197. blocks, closures, and proc 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
  • 198. 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
  • 199. Active Records Contd. 199
  • 200. 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
  • 201. 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
  • 202. 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
  • 203. 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
  • 204. 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
  • 205. 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
  • 206. 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
  • 207. Update/Create Member.create(:nick_name => 'Salaar', expiry_date => Time.now + 1.year) member.nick_name = 'Mikael' member.save! 207
  • 208. Destroy/Delete Destroy instantiates object and calls all hooks Delete simply executes the delete query 208
  • 209. Available Callbacks • Creating/Updating an Object • before_validation • after_validation • before_save • after_save • Destroying an Object • before_destroy • after_destroy • around_destroy 209
  • 210. Transactions •Account.transaction do account1.deposit(100) account2.withdraw(100) end Account.transaction(account1, account2) do account1.deposit(100) account2.withdraw(100) end 210
  • 211. Calculations Person.minimum(‘age’) Person.maximum(‘age’) Person.sum(‘age’) Person.where(*“age > ?”, 25+).count Person.average(‘age’) 211
  • 212. 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
  • 213. Why Associations? • Allows to define associations between different models. For example: • Firm has many Customers • Customer has many Orders • Order belongs to Customer 213
  • 214. Types of Associations • belongs_to • has_one • has_many • has_many :through • has_one :through • has_and_belongs_to_many 214
  • 215. 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
  • 216. 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
  • 217. 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
  • 218. 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
  • 219. 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
  • 220. 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
  • 221. 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
  • 222. Has and Belongs to Many • Creates a direct many-to-many connection with another model, with no intervening model. 222
  • 223. 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.
  • 224. 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
  • 225. 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 functio nally equival ent
  • 226. 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
  • 227. 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
  • 228. 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
  • 229. Methods Added by Has Many/Has and Belongs to Manycollection.clear Removes every object from the collection collection.empty? collection.size collection.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 type 229
  • 230. 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
  • 231. 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
  • 232. Joining and Searching • Group.joins(:group_members).where(:name=>'ror- training',:group_members=>{:is_muted => false}) 232
  • 233. Validation Validations are used to ensure that only valid data is saved into your database. 233
  • 234. 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
  • 235. 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
  • 236. 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
  • 237. 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
  • 238. ActiveRecord Validation Triggers create create! save save! update update_attributes update_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
  • 239. ActiveRecord Validation Skipping save(:validate => false) decrement! decrement_counter increment! increment_counter toggle! touch update_all update_attribute update_column update_counters 239
  • 240. ActiveRecord Valid? class Person < ActiveRecord validates :name, :presence => true end Person.create(:name => "John Doe").valid? # => true Person.create(:name => nil).valid? # => false Person.new(:name => nil).valid? # => false 240
  • 241. 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
  • 242. 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
  • 243. 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
  • 244. validates_presence_of • Validates that the specified attributes are not empty. • class Person < ActiveRecord::Base • validates_presence_of :name, :login, :email • end 244
  • 245. 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
  • 246. 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
  • 247. 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
  • 248. 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
  • 249. 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
  • 250. 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
  • 251. 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
  • 252. 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
  • 253. 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.
  • 254. 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
  • 255. Live Demo - Contd 255
  • 256. 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
  • 257. 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
  • 258. 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
  • 259. Can view, and add comments. What about delete? 259
  • 260. 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
  • 261. Associated comments should get deleted when post is deleted? 261
  • 262. 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
  • 263. 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
  • 264. 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
  • 265. 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
  • 266. 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
  • 267. AJAX 267
  • 268. 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
  • 269. 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
  • 270. 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
  • 271. 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
  • 272. 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
  • 273. 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
  • 274. 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/bas e/jquery-ui.css to public/stylesheets/ 274
  • 275. 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
  • 276. 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
  • 277. 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
  • 278. 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
  • 279. 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
  • 280. 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
  • 281. 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
  • 282. Security 282
  • 283. 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
  • 284. 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
  • 285. 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
  • 286. 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
  • 287. 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
  • 288. Autheticate Method • Should take in email and password. • Returns the user if password matched, nil otherwise 288
  • 289. 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("bar@foo.com", @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
  • 290. 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
  • 291. 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
  • 292. 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
  • 293. Day 4 293
  • 294. 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
  • 295. 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
  • 296. 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
  • 297. 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
  • 298. 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
  • 299. 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
  • 300. 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
  • 301. 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
  • 302. Capistrano 302
  • 303. 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
  • 304. 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
  • 305. 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
  • 306. 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
  • 307. 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
  • 308. 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
  • 309. Caching 309
  • 310. RoR Caching • Page Caching • Action Caching • Fragment Caching • set config.action_controller.perform_caching = true in config/environments/* 310
  • 311. 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
  • 312. Products Page Caching • class ProductsController < ActionController • caches_page :index • def index • @products = Products.all • end • def create • expire_page :action => :index • end • end 312
  • 313. 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
  • 314. 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
  • 315. 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
  • 316. 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
  • 317. 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
  • 318. 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
  • 319. 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
  • 320. 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
  • 321. 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
  • 322. 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
  • 323. 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
  • 324. 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
  • 325. Database Optimizations 325
  • 326. 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
  • 327. 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
  • 328. 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
  • 329. 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
  • 330. Database Indexing 330
  • 331. 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
  • 332. 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
  • 333. 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
  • 334. 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
  • 335. Amazon S3 335
  • 336. 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
  • 337. 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
  • 338. Paperclip 338
  • 339. 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
  • 340. 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
  • 341. 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
  • 342. Setting up Paperclip • ImageMagick must be installed • Include the gem in your Gemfile: • gem "paperclip", "~> 2.3" 342
  • 343. 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
  • 344. 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
  • 345. 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
  • 346. 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
  • 347. 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
  • 348. Master/Slave Replication 348
  • 349. 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
  • 350. 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
  • 351. 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
  • 352. Grids 352
  • 353. 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
  • 354. 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
  • 355. 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
  • 356. 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
  • 357. 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
  • 358. 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
  • 359. 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
  • 360. Thinking Sphinx 360
  • 361. 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
  • 362. 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
  • 363. Installing Sphinx • http://sphinxsearch.com/downloads/ • ./configure • make • sudo make install 363
  • 364. Thinking Sphinx • A Ruby connector between Sphinx and ActiveRecord. • gem 'thinking-sphinx', '2.0.3' 364
  • 365. 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
  • 366. 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
  • 367. 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
  • 368. 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 => true 368
  • 369. 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
  • 370. Conditions and Grouping • Possible add custom conditions or groupings • define_index do • # ... • where "status = 'active'" • group_by "user_id" • end 370
  • 371. 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 371
  • 372. Basic and Conditional Searching • Basic Searching • Article.search 'pancakes' • Field Conditions • Article.search :conditions => {:subject => 'pancakes'} • Article.search 'pancakes', :conditions => {:subject => 'tasty'} 372
  • 373. 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
  • 374. 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
  • 375. 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
  • 376. Boolean Match Mode • #ANDs are used implicitly if no logic is given • #search for ‘pancakes & waffles’ • Article.search 'pancakes &amp; 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
  • 377. 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
  • 378. 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
  • 379. 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
  • 380. 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
  • 381. 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
  • 382. 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
  • 383. 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
  • 384. 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
  • 385. 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
  • 386. 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
  • 387. 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
  • 388. 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
  • 389. 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
  • 390. 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
  • 391. 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
  • 392. 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
  • 393. 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
  • 394. 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
  • 395. Messaging Queues Reference: http://www.slideshare.net/skyfallsin/message- queues-in-ruby-an-overview 395
  • 396. 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
  • 397. 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
  • 398. 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
  • 399. 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
  • 400. 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
  • 401. 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
  • 402. 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
  • 403. 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
  • 404. 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
  • 405. 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
  • 406. 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
  • 407. 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
  • 408. 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
  • 409. 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
  • 410. 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
  • 411. Resque Frontend • Provides statistics to monitor resque 411
  • 412. 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
  • 413. AJAX Contd. 413
  • 414. 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
  • 415. 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/theme s/base/jquery-ui.css 415
  • 416. Periodically Call Remote Function • Periodically call remote function • $(document).ready( • function(){ • setInterval(function(){ • $('#mydiv').load('/controller/action'); • }, 3000); • }); 416
  • 417. 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
  • 418. 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
  • 419. 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
  • 420. 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
  • 421. 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
  • 422. 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
  • 423. Security Contd. 423
  • 424. reCAPTCHA 424
  • 425. 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
  • 426. 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
  • 427. 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
  • 428. 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
  • 429. 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
  • 430. 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
  • 431. 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
  • 432. 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
  • 433. 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
  • 434. Testing Contd. 434
  • 435. 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
  • 436. 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[user@foo.com THE_USER@foo.bar.org first.last@foo.jp] • 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
  • 437. 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
  • 438. 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-zd-.]+.[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
  • 439. 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
  • 440. 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
  • 441. 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
  • 442. 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
  • 443. 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
  • 444. 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
  • 445. 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 => "user@example.com", • 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
  • 446. 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
  • 447. 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
  • 448. 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
  • 449. Active Resource 449
  • 450. 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
  • 451. 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
  • 452. 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
  • 453. References http://marklunds.com/s5/rails101/html/rails_introduction.html http://www.troubleshooters.com/codecorn/ruby/symbols.htm http://www.techotopia.com/index.php/Ruby_Essentials http://www.slideshare.net/vishnu/the-ruby-programming-language-or-why-are-you-wasting-brain-power http://guides.rubyonrails.org/association_basics.html http://guides.rubyonrails.org/layouts_and_rendering.html http://guides.rubyonrails.org/routing.html http://guides.rubyonrails.org/active_record_validations_callbacks.html http://guides.rubyonrails.org/getting_started.html http://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/en http://www.slideshare.net/skyfallsin/message-queues-in-ruby-an-overview https://github.com/defunkt/resque Rails 3 in a Nutshell: http://ofps.oreilly.com/titles/9780596521424/index.html http://railscasts.com/episodes/158-factories-not-fixtures http://leikind.org/pages/wicegrid http://awesomeful.net/posts/47-sortable-lists-with-jquery-in-rails http://tomafro.net/2009/08/using-indexes-in-rails-index-your-associations http://weblog.jamisbuck.org/2006/10/23/indexing-for-db-performance http://www.simonecarletti.com/blog/2010/06/unobtrusive-javascript-technique/ 453