Federico Bond - tagged with other http://www.federicobond.com.ar/feed en-us http://blogs.law.harvard.edu/tech/rss Sweetcron federicobond+lifestream@gmail.com Zero-to-Sixty: Creating and Deploying a Rails App in Under an Hour http://www.federicobond.com.ar/items/view/463/zero-to-sixty-creating-and-deploying-a-rails-app-in-under-an-hour

Give me an hour of your time, and I’ll take you on a fly by of the Ruby on Rails framework. We’ll create controllers, models, views, add admin logins, and deploy using Heroku’s service in under an hour! In this article we’ll create a simple bookshelf application where you can add books and write thoughts about them. Then we’ll deploy the application in just a few minutes. So buckle up because this article moves fast! This article assumes that you may know what Ruby on Rails is, but not exactly how it works. This article doesn’t describe in-depth how each step works, but it does describe what we need to do, then the code to do that. Zero Ruby on Rails is a full stack MVC web application framework. Full stack means you get everything: a simple web server you can use to test your apps, a database layer, testing framework, and an MVC based design. MVC stands for Model-View-Controller.Model A model stores information. Models are stored in the database. Rails supports MySQL, PostgreSQL, or SQLite. Each model has its own class and table. Say we want to model a “game.” A game has things like number of players, a start time, end time, teams playing, and a winner. These attributes become columns in the “games” table. Table names are lowercase, underscored, and pluralized. The model’s class name is Game. In Rails you create models through migrations and generators. A migration describes how to add/remove columns and tables from the database.Controller A controller is the manager. It takes information and does some logic like CRUD, or maybe import some stuff from a file, add/remove permissions–you name it a controller can do it. Controllers are the part of your app that does. How do we call controllers? Rails uses routes. A route is a formatted url that is tied to an action with a set of parameters. Going back to the Game model, we need a controller for functionality. At some point we’ll to need to list all the games in the system. A basic REST url for this route looks like “/games” How does Rails know what controller to look for and what action to call? It looks at your routes.rb file. You may have a route that looks like this “GET /makes {:name => ‘games’, :action => ‘index’”}. This translates to GamesController and it’s index method. Just like models, class names are CamelCase and file names are underscored. So our GamesController would be stored in /app/controllers/games_controller.rb. After the logic, the controller renders a view.View A view is the easiest part to understand. It’s what you see. It’s the HTML you generate to show the user what they need. Views are ERB templates. ERB stands for Embedded Ruby. You use ERB similar to how you embed php into a document. If you want to insert an instance variable @game.time into some html write <%= @game.time %> Ten First install Rails. Installing Rails is very easy depending on your platform. If you are on a Linux/OSX, it’s no problem. Windows is more complicated and I have no experience with it. This section will give you a brief overview of installing Rails through RubyGems, the Ruby package manager. A gem is a bundle of ruby code in a package that can be used in your programs. For UNIX based system, install RubyGems, then install the Rails gem. This process will go something like this: # ubuntu sudo apt-get install rubygems # fedora sudo yum install rubygems # gentoo sudo emerge rubygems # OSX sudo port install rubygems

# after you have rubygems installed
sudo gem install gemcutter # ruby gem hosting service
sudo gem tumble
sudo gem install Rails

Here are some links to help you through the setup process Instant Rails, like instant LAMP but for Rails Ubuntu Community Docs about Ruby on Rails Getting Rails running on Windows Snow Leopard on Rails by a guy I know The mandatory google link Once you can run the “rails” command you’re ready for the next step. Fifteen Now it’s time to install database support before we get started. Rails has support for all the popular DB’s, but for this example we’ll use SQLite because it’s lightweight.. Depending on your platform (again) installing sqlite support can be easy or painful. It can be a pain since the gem has to be built against C extensions, which means the sqlite3 package has to be installed on your system. Again the process will go something like this: # ubuntu sudo apt-get install sqlite3 sqlite3-devel # fedora sudo yum install sqlite3 sqlite3-devel # OSX sudo port install sqlite3

# then once you have the sqlite3 package installed
sudo gem install sqlite3-ruby

Read the previous links if you’re having problems with these steps. They describe installing sqlite as well. Twenty Time to generate our app. The rails command creates a base application structure. All we need to do is be in a directory and run it like so: $ cd ~/projects $ Rails bookshelf #this will create a new directory named bookshelf that holds our app $ cd bookshelf It’s important to note that the Rails default is an SQLite based app. You may be thinking, what if I don’t want that? The rails command is a generator. All it does is copy stored files into a new directory. By default it will create sqlite3 databases in /bookshelf/db/development.sqlite3, /bookshelf/db/production.sqlite3, and /bookshelf/db/testing.sqlite3. Database connection information is stored in /bookshelf/config/database.yml. You don’t need to edit this file since it contains default information for an sqlite setup. It should look like this: # SQLite version 3.x # gem install sqlite3-ruby (not necessary on OS X Leopard) development: adapter: sqlite3 database: db/development.sqlite3 pool: 5 timeout: 5000

# Warning: The database defined as "test" will be erased and
# re-generated from your development database when you run "rake".
# Do not set this db to the same as development or production.
test:
  adapter: sqlite3
  database: db/test.sqlite3
  pool: 5
  timeout: 5000

production:
  adapter: sqlite3
  database: db/production.sqlite3
  pool: 5
  timeout: 5000

Notice there are different environments assigned. Rails has three modes: Development, Testing, and Production. Each has different settings and databases. Development is the default environment. At this point we can start our app to make sure it’s working. You can see there’s a directory called /script. This directory contains ruby scripts for interacting with our application. Some important ones are /script/console, and /script/server. We will use the /script/server command to start a simple server for our application. bookshelf $ ./script/server # then you should see something like this. Rails will start a different server depending on your platform, but it should look something like this: => Booting Mongrel => Rails 2.3.5 application starting on http://0.0.0.0:3000 => Call with -d to detach => Ctrl-C to shutdown server Time to visit the application. Point your browser to “http://localhost:3000″ and you should see this splash page: You’re riding on Rails. Now that the code working on a basic level, it’s time to delete the splash page and get started with some code. bookshelf $ rm /public/index.html Twenty Five Our application needs data. Remember what this means? It means models. Great, but how we generate a model? Rails comes with some generators to common tasks. The generator is the file /script/generate. The generator will create our model.rb file along with a migration to add the table to the database. A migration file contains code to add/drop tables, or alter/add/remove columns from tables. Migrations are executed in sequence to create the tables. Run migrations (and various other commands) with “rake”. Rake is a ruby code runner. Before we get any further, let’s start by defining some basic information for the books. A book has these attributes:Title : StringThoughts : Text That’s enough to start the application. Start by generating a model with these fields using the model generator: bookshelf $ ./script/generate model Book title:string thoughts:text # notice how the attributes/types are passed to the generator. This will automatically create a migration for these attributes # These are optional. If you leave them out, the generator will create an empty migration. exists app/models/ exists test/unit/ exists test/fixtures/ create app/models/book.rb create test/unit/book_test.rb create test/fixtures/books.yml create db/migrate create db/migrate/20091202052507_create_books.rb # The generator created all the files we need to get our model up and running. We need to pay the most attention to these files: # app/models/book.rb # where our code resides # db/migrate/20091202052507_create_books.rb # code to create our books table. Open up the migration file: class CreateBooks < ActiveRecord::Migration def self.up create_table :books do |t| t.string :title t.text :thoughts

    t.timestamps
  end
end

def self.down
  drop_table :books
end

end Notice the create_table :books block. This is where columns are created. An id primary key is created automatically. t.timestamps adds columns for created_at and updated_at. Now, run the migration using the rake task db:migrate. db:migrate applies pending migrations: bookshelf $ rake db:migrate == CreateBooks: migrating ==================================================== -- create_table(:books) -> 0.0037s == CreateBooks: migrated (0.0038s) =========================================== Cool, now we have a table, let’s create a dummy book just for kicks in the console. The Rails console uses IRB (interactive ruby) and loads all classes for your project. IE you can access to all your models. Open the console like this: bookshelf $ ./script/console >> # let's create a new model. You can specify a hash of assignments in the constructor to assign values like this: >> book = Book.new :title => 'Rails is awesome!' , :thoughts => 'Some sentence from a super long paragraph' => #<Book id: nil, title: "Rails is awesome!", thoughts: "Some sentence from a super long paragraph", created_at: nil, updated_at: nil> # and ruby will display it back >> book.save => true # now are book is saved in the database. We can query it like this: >> Book.all # find all books and return them in an array => [#<Book id: 1, title: "Rails is awesome!", thoughts: "Some sentence from a super long paragraph", created_at: "2009-12-02 06:05:38", updated_at: "2009-12-02 06:05:38">] >> exit # now that our model is saved, let's exit the console. Now that we can create books, we need some way to show them to the user Thirty Remember controllers? We need a controller to display all the books in the system. This scenario corresponds to the index action in our BooksController (books_controller.rb) which we don’t have yet. Just like generating models, use a generator to create the controller: bookshelf $ ./script/generate controller Books exists app/controllers/ exists app/helpers/ create app/views/books exists test/functional/ create test/unit/helpers/ create app/controllers/books_controller.rb create test/functional/books_controller_test.rb create app/helpers/books_helper.rb create test/unit/helpers/books_helper_test.rb # notice Rails created the file app/controllers/books_controller.rb? This is where we are going to define our actions or methods for the BooksController class We need to define an action that finds and displays all books. How did we find all the books? Earlier we used Book.all. Our strategy is use Book.all and assign it to an instance variable. Why an instance variable? We assign instance variables because views are rendered with the controllers binding. You’re probably thinking bindings and instance variables…what’s going on? Views have access to variables defined in actions but only instance variables. Why, because instance variables are scoped to the object and not the action. Let’s see some code: class BooksController < ApplicationController # notice we've defined a method called index for a BooksController instance. We tie this together with routes def index @books = Book.all # instance variables are prefixed with an @. If we said books = Book.all, we wouldn't be able to access books in the template end end Now the controller can find all the books. But how do we tie this to a url? We have to create some routes. Rails comes with some handy functions for generating RESTful routes (another Rails design principle). This will generate urls like /makes and /makes/1 combined with HTTP verbs to determine what method to call in our controller. Use map.resources to create RESTful routes. Open up /config/routes.rb and change it to this: ActionController::Routing::Routes.draw do |map| map.resources :books end Routes.rb can look arcane to new users. Luckily there is a way to decipher this mess. There is routes rake task to display all your routing information. Run that now and take a peek inside: bookshelf $ rake routes books GET /books(.:format) {:controller=>"books", :action=>"index"} POST /books(.:format) {:controller=>"books", :action=>"create"} new_book GET /books/new(.:format) {:controller=>"books", :action=>"new"} edit_book GET /books/:id/edit(.:format) {:controller=>"books", :action=>"edit"} book GET /books/:id(.:format) {:controller=>"books", :action=>"show"} PUT /books/:id(.:format) {:controller=>"books", :action=>"update"} DELETE /books/:id(.:format) {:controller=>"books", :action=>"destroy"} # as you can see this command can display a lot of information. On the left column we have a helper to generate a url, then the HTTP verb associated with the url, then the url, and finally the controller and action to call. # for example GET /books will call BooksController#index or # GET /books/1 will call BooksController#show # the url helpers are very important but we'll get to them later. For now know that we are going to create a /books page to list all books Now we need to create a template to display all our books. Create a new file called /app/views/books/index.html.erb and paste this: <% for book in @books do %> <h2><%=h book.title %></h2> <p><%= book.thoughts %></p> <% end %> This simple view loops over all @books and displays some HTML for each book. Notice a subtle difference. <%= is used when we need to output some text. <% is used when we aren’t. If you don’t follow this rule, you’ll get an exception. Also notice the h before book.title. h is a method that escapes HTML entities. If you’re not familiar with ruby, you can leave off ()’s on method calls if they’re not needed. h text translates to: h(text). Time to run the server and see what we’ve got. Start the server, then go to http://localhost/books. bookshelf $ ./script/server If all goes according to plan you should see some basic HTML. Thirty Five We have one book in our system, but we need some more books to play with. There are a few ways to go about doing this. I like the forgery gem. Forgery can create random strings like names, or lorem ipsum stuff. We are going to set a gem dependency in our app, install the gem, then use it to create a rake task to populate our data. Step 1: open up /config/environment.rb and add this line: config.gem 'forgery'

# now let's tell Rails to install all gems dependencies for this project # gem install gemcutter # if you haven't already # gem tumble # again, if you haven't already bookshelf $ sudo rake gems:install Now we’re going to use the Forgery classes to create some fake data. The Forgery documentation is here. We’ll use the LoremIpsumForgery to create some basic data. We can define our own rake tasks by creating a .rake file in /lib/tasks. So create a new file /lib/tasks/populate.rake: begin namespace :db do desc "Populate the development database with some fake data" task :populate => :environment do 5.times do Book.create! :title => Forgery::LoremIpsum.sentence, :thoughts => Forgery::LoremIpsum.paragraphs(3) end end end rescue LoadError puts "Please run: sudo gem install forgery" end This rake task will create five fake books. Notice I added a begin/rescue. When you run a rake task it looks at all possible tasks in the rake initialization. If you were to run any rake task before you installed the gem, rake would blow up. Wrapping it in an begin/rescue stop rake from aborting. Run the task to populate our db: bookshelf $ rake db:populate bookshelf $ ./script/console # let's enter the console to verify it all worked >> Book.all => [#<Book id: 1, title: "Rails is awesome!", thoughts: "Some sentence from a super long paragraph", created_at: "2009-12-02 06:05:38", updated_at: "2009-12-02 06:05:38">, #<Book id: 2, title: "Lorem ipsum dolor sit amet, consectetuer adipiscing...", thoughts: "Lorem ipsum dolor sit amet, consectetuer adipiscing...", created_at: "2009-12-02 07:28:05", updated_at: "2009-12-02 07:28:05">, #<Book id: 3, title: "Lorem ipsum dolor sit amet, consectetuer adipiscing...", thoughts: "Lorem ipsum dolor sit amet, consectetuer adipiscing...", created_at: "2009-12-02 07:28:05", updated_at: "2009-12-02 07:28:05">, #<Book id: 4, title: "Lorem ipsum dolor sit amet, consectetuer adipiscing...", thoughts: "Lorem ipsum dolor sit amet, consectetuer adipiscing...", created_at: "2009-12-02 07:28:05", updated_at: "2009-12-02 07:28:05">, #<Book id: 5, title: "Lorem ipsum dolor sit amet, consectetuer adipiscing...", thoughts: "Lorem ipsum dolor sit amet, consectetuer adipiscing...", created_at: "2009-12-02 07:28:05", updated_at: "2009-12-02 07:28:05">, #<Book id: 6, title: "Lorem ipsum dolor sit amet, consectetuer adipiscing...", thoughts: "Lorem ipsum dolor sit amet, consectetuer adipiscing...", created_at: "2009-12-02 07:28:05", updated_at: "2009-12-02 07:28:05">] >> exit Start the server again and head back to the /books pages. You should see: Now we have a listing of more than one book. What if we have a lot of books? We need to paginate the results. There’s another gem for this. The gem is ‘will_paginate.’ Following the same steps as before, let’s add a gem dependency for ‘will_paginate’ and rake gems:install: # in environment.rb config.gem 'will_paginate' # from terminal bookshelf $ sudo rake gems:install # then let's add more books to our db bookshelf $ rake db:populate # run this a few times to get a large sample, or change the number in rake file Head back to your /books page and you should be bombarded by books at this point. It’s time to add pagination. Pagination works at two levels. The controller decides which books should be in @books, and the view should display the pagination links. The will_paginate helper makes this very easy. We’ll use the .paginate method and the will_paginate view helper to render page links. All it takes is two lines of code. # books_controller.rb, change the previous line to: @books = Books.paginate :page => params[:page], :per_page => 10

# index.html.erb, add this line after the loop: <%= will_paginate @books %> Head back to your /makes page and you should see some pagination links (given you have more than 10 books) Sweet! We are movin’ through this app. It’s time to spruce up our page a little bit. One key Rails principle is DRY (Do Not Repeat Yourself). We could work through the exercise of doing some basic CSS to get a page looking OK, or we could keep things DRY and use some code to do it for us. We are going to use Ryan Bate’s nifty-generators gem to generate a layout for the site. A layout is a template your views can fill in. For example we can use a layout to determine the over all structure of a page, then define where the views fill it in. Since this isn’t a project dependency, we don’t have to add it to environment.rb. We can just install it regularly. # console $ sudo gem install nifty-generators Run the generator to create a layout file and stylesheets. $ ./script/generate nifty_layout exists app/views/layouts exists public/stylesheets exists app/helpers create app/views/layouts/application.html.erb # this is our layout file create public/stylesheets/application.css # css file that styles the layout create app/helpers/layout_helper.rb # view helpers needed in the generator Take a look at the application.html.erb file and see what’s inside: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html> <head> <title><%= h(yield(:title) || "Untitled") %></title> <%= stylesheet_link_tag 'application' %> <%= yield(:head) %> </head> <body> <div id="container"> <%- flash.each do |name, msg| -%> <%= content_tag :div, msg, :id => "flash_#{name}" %> <%- end -%>

    &lt;%- if show_title? -%&gt;
      &lt;h1&gt;&lt;%=h yield(:title) %&gt;&lt;/h1&gt;
    &lt;%- end -%&gt;

    &lt;%= yield %&gt;
  &lt;/div&gt;
&lt;/body&gt;

</html> See those yields? That’s where a view fills in the layout. The last yield has no argument. Default content goes there. Yields with an argument must have content defined using content_for. Fix up index.html.erb view to go with the new layout: <% title 'My Books' %>

<% for book in @books do %> <h2><%=h book.title %></h2> <p><%= book.thoughts %></p> <% end %>

<%= will_paginate @books %> All we did was add the title method which sets the title for a page. The title helper calls content_for :title and sets it to the argument. Our view fills in the last yield. Check out the results! Forty Now that our application is looking better, let’s add some interaction. In typical Web 2.0 style we’re going to allow users to comment on our content, but we aren’t going to require the user to register. We need to create new model called Comment. A comment is going to have some text, an author, and an associated Book. How do we link these two models together? Associations. Rails provides these associations: belongs_to, has_many, has_one, and has_and_belongs_to many. It should be easy to see that a book has many comments, and a comment belongs_to a book. So we’ll use a generator to create the comment model and migration: $ ./script/generate model Comment text:text author:string exists app/models/ exists test/unit/ exists test/fixtures/ create app/models/comment.rb create test/unit/comment_test.rb create test/fixtures/comments.yml exists db/migrate create db/migrate/20091202081421_create_comments.rb Astute readers will notice that this migration is lacking the foreign key column. We’ll have to add that ourselves. Open up your create_comments.rb migration: class CreateComments < ActiveRecord::Migration def self.up create_table :comments do |t| t.text :text t.string :author t.belongs_to :book # creates a new integer column named book_id t.timestamps end end

def self.down
  drop_table :comments
end

end

# now migrate your database $ rake db:migrate rake db:migrate (in /Users/adam/Code/bookshelf) == CreateComments: migrating ================================================= -- create_table(:comments) -> 0.0021s == CreateComments: migrated (0.0022s) ======================================== Now it’s time to associate our models using the Rails associations. We’ll call the method inside the model’s class body. Rails uses metaprogramming to generate the methods needed to make our association work. We’ll edit our comment.rb and book.rb files: # book.rb class Book < ActiveRecord::Base has_many :comments end

# comment.rb class Comment < ActiveRecord::Base belongs_to :book end Now Book instances have a method .comments with returns an array of all its comments. Comment instances have a method called .book that return the associated book. Use the << operator to add objects to arrays. Let’s see how it works in the console: $ ./script/console >> book = Book.find(1) => #<Book id: 1, title: "Rails is awesome!", thoughts: "Some sentence from a super long paragraph", created_at: "2009-12-02 06:05:38", updated_at: "2009-12-02 06:05:38"> >> comment = Comment.new :text => "This is an comment", :author => "Adam" => #<Comment id: nil, text: "This is an comment", author: "Adam", book_id: nil, created_at: nil, updated_at: nil> >> book.comments << comment => [#<Comment id: 1, text: "This is an comment", author: "Adam", book_id: 1, created_at: "2009-12-02 08:25:47", updated_at: "2009-12-02 08:25:47">] >> book.save => true >> book.comments => [#<Comment id: 1, text: "This is an comment", author: "Adam", book_id: 1, created_at: "2009-12-02 08:25:47", updated_at: "2009-12-02 08:25:47">] >> comment.book => #<Book id: 1, title: "Rails is awesome!", thoughts: "Some sentence from a super long paragraph", created_at: "2009-12-02 06:05:38", updated_at: "2009-12-02 06:05:38"> >> exit In the console session I found one of the existing books, then created a new comment. Next I added it to the book.comments. Then I save book. The book must be saved for the association to be stored. What’s next? We need to create a page where the user can view a book and all it comments. That page should also have a form where the user can add their comment. Create a new action in the books controller to show the details for a specified book. The book is found by id. Pop into books_controller.rb and add this: def show @book = Book.find params[:id] end Make a new template for this action at /app/views/books/show.html.erb and paste this: <% title @book.title %>

<h2><%=link_to(h(@book.title), book_path(@book)) %></h2> <p><%= @book.thoughts %></p> Now let’s add some links for the index actions to link the show action: # update index.html.erb contents to: <% title 'My Books' %>

<% for book in @books do %> <h2><%=link_to(h(book.title), book_path(book)) %></h2> <p><%= book.thoughts %></p> <% end %>

<%= will_paginate @books %> Remember our url helpers from rake routes? We’re using book_path to generate a url to the book controller’s show actions. If you don’t remember check rake routes again. link_to is a helper to generate a link tag. Now let’s fire up our server and click through the app. Now you should have some ugly blue links. Click on your book title and it should go to /books/:id aka BooksController#show: Time to display some comments. Remember that console session we did a little bit back? One of our books has some comments. let’s update our controller to find the comments and our show.html.erb to display them. # books_controller.rb def show @book = Book.find(params[:id]) @comments = @book.comments end

# show.html.erb <% title @book.title %>

<h2><%=link_to(h(@book.title), book_path(@book)) %></h2> <p><%= @book.thoughts %></p>

<% if @comments %> <h3>Comments</h3>

&lt;% for comment in @comments do %&gt;
  &lt;p&gt;&lt;strong&gt;&lt;%=h(comment.author) %&gt;&lt;/strong&gt;: &lt;%=h comment.text %&gt;
&lt;% end %&gt;

<% end %> So we assign @comments in the controller to be all the book’s comments, then do a loop in the view to display them. Head over to /books/1 (1 came from Book.find(1) in the console session). Check this out: Now we need the form to create a new comment. We need two things. 1, A comments controller to save the comment, and 2 a route to that action. let’s tackle #1 first. bookshelf $ ./script/generate controller Comments Create action that instantiates a new comment, sets its attributes (text/author) from the submitted form data, and saves it. class CommentsController < ApplicationController def create book = Book.find params[:book_id] comment = book.comments.new params[:comment] comment.save flash[:notice] = 'Comment saved' redirect_to book_path(book) end end First the code finds the book, then creates a new comment form the form data, saves it, sets a message, then redirects back to that book’s page. params holds a hash of all GET/POST data with a request. Now we need to create a route to the controller’s action. Open up routes.rb: ActionController::Routing::Routes.draw do |map| map.resources :books do |book| book.resources :comments, nly => :create end end

bookshelf $ rake routes # We needed to add a route to create a new comment for a book. We need to know what book we are creating a comment for, so we need a book_id in the route. Look at the book_comment line. # book_comment is tied to our CommentsController#create book_comments POST /books/:book_id/comments(.:format) {:controller=>"comments", :action=>"create"} books GET /books(.:format) {:controller=>"books", :action=>"index"} POST /books(.:format) {:controller=>"books", :action=>"create"} new_book GET /books/new(.:format) {:controller=>"books", :action=>"new"} edit_book GET /books/:id/edit(.:format) {:controller=>"books", :action=>"edit"} book GET /books/:id(.:format) {:controller=>"books", :action=>"show"} PUT /books/:id(.:format) {:controller=>"books", :action=>"update"} DELETE /books/:id(.:format) {:controller=>"books", :action=>"destroy"} # every time you modify routes.rb you'll need to restart the server # kill the server process you have running with ^c (ctrl + c) and start it again Head back to the /books page and make sure nothing has blown up. Everything should be fine and dandy. Now for constructing the form. We need a form that submits POST data to /book/:book_id/comments. Luckily Rails has the perfect helper for this: form_for. form_for takes some models and generates a route for them. We pass form_for a block to create form inputs. Go ahead and paste this into the bottom of your show.html.erb: <h3>Post Your Comment</h3> <% form_for([@book, Comment.new]) do |form| %> <p><%= form.label :author %></p> <p><%= form.text_field :author %></p>

&lt;p&gt;&lt;%= form.label :text, &#39;Comment&#39; %&gt;&lt;/p&gt;
&lt;p&gt;&lt;%= form.text_area :text %&gt;&lt;/p&gt;

&lt;%= form.submit &#39;Save&#39; %&gt;

<% end %> We call form_for to create a new form for the book’s comment, then use the text_field/text_area to create inputs for attributes. At this point we can go ahead and make a comment. Fill in the form and viola you now have comments! See that green thing? That’s the flash. The flash is a way to store messages between actions. It’s perfect for storing little messages like this. But what do we do if a book has too many comments? We paginate them just like did before. So let’s make some changes to the controller and view: # books_controller.rb def show @book = Book.find(params[:id]) @comments = @book.comments.paginate :page => params[:page], :per_page => 10, rder => 'created_at ASC' end

# show.html.erb

<% title @book.title %>

<h2><%=link_to(h(@book.title), book_path(@book)) %></h2> <p><%= @book.thoughts %></p>

<% if @comments %> <h3>Comments</h3>

&lt;% for comment in @comments do %&gt;
  &lt;p&gt;&lt;strong&gt;&lt;%=h(comment.author) %&gt;&lt;/strong&gt;: &lt;%=h comment.text %&gt;
&lt;% end %&gt;

&lt;%= will_paginate @comments %&gt;

<% end %>

<h3>Post Your Comment</h3> <% form_for([@book, Comment.new]) do |form| %> <p><%= form.label :author %></p> <p><%= form.text_field :author %></p>

&lt;p&gt;&lt;%= form.label :text, &#39;Comment&#39; %&gt;&lt;/p&gt;
&lt;p&gt;&lt;%= form.text_area :text %&gt;&lt;/p&gt;

&lt;%= form.submit &#39;Save&#39; %&gt;

<% end %> Start commenting on your books and you should see some pagination. Now people can comment, and everything is paginated but we’re missing something. We have no web interface for creating books. We need to create a form for that. Also we are the admin so only I should be allowed to create books. This means we need to create a user, login, and check to see if they can do an action. Fifty Now we’re going to implement CRUD functionality for admins. First we’ll implement actions to create, edit, and delete books. Then we’ll create an admin login. Finally we’ll make sure only admins can do those actions. Creating a new books requires two new actions. One action that renders a form for a new book. This action is named ‘new’. The second is named ‘create.’ This action takes the form parameters and saves them in the database. Open up your books_controller.rb and add these actions: def new @book = Book.new end

def create @book = Book.new params[:book] if @book.save flash[:notice] = "#{@book.title} saved." redirect_to @book else render :new end end We also need a new view that shows a form. Create a new file /apps/views/books/new.html.erb and paste this: <% form_for(@book) do |form| %> <p> <%= form.label :title %><br/> <%= form.text_field :title %> </p>

&lt;p&gt;
  &lt;%= form.label :thoughts %&gt;&lt;br/&gt;
  &lt;%= form.text_area :thoughts %&gt;
&lt;/p&gt;

&lt;%= form.submit %&gt;

<% end %> Now we’re ready to create a new book. Point your browser to /books/new and you should see this form. Go a head and create a new book. After you fill in your form you should see your new book. Get rid of the double header in /app/views/books/show.html.erb and add some links to actions an admin can do on that book. Open up that file and set it’s contents to: <% title @book.title %>

<p><%= @book.thoughts %></p>

<% if @comments %> <h3>Comments</h3>

&lt;% for comment in @comments do %&gt;
  &lt;p&gt;&lt;strong&gt;&lt;%=h(comment.author) %&gt;&lt;/strong&gt;: &lt;%=h comment.text %&gt;
&lt;% end %&gt;

&lt;%= will_paginate @comments %&gt;

<% end %>

<h3>Post Your Comment</h3> <% form_for([@book, Comment.new]) do |form| %> <p><%= form.label :author %></p> <p><%= form.text_field :author %></p>

&lt;p&gt;&lt;%= form.label :text, &#39;Comment&#39; %&gt;&lt;/p&gt;
&lt;p&gt;&lt;%= form.text_area :text %&gt;&lt;/p&gt;

&lt;%= form.submit &#39;Save&#39; %&gt;

<% end %>

<p> Admin Actions: <%= link_to 'Edit', edit_book_path(@book) %> | <%= link_to 'Delete', book_path(@book), :method => :delete, :confirm => "Are you sure?" %> </p> Head over to a book’s page and you should see: Now that we have some links to edit and delete, you can implement them. Editing a book works just about the same as creating a new one. We need an action that shows an edit form, and one to save the changes. Delete is just one action that deletes the record from the database. Open up books_controller.rb and add these actions: def edit @book = Book.find params[:id] end

def update @book = Book.find params[:id] if @book.update_attributes(params[:book]) flash[:notice] = "#{@book.title} saved." redirect_to @book else render :edit end end

def destroy book = Book.find params[:id] book.destroy flash[:notice] = "#{book.title} deleted." redirect_to books_path end The edit action finds the requested book from the id in the url. The update action finds the book from the id and uses the update_attributes method to set the new values from the form. Delete finds the book by id and deletes it. Then it redirects you back to the books listing. Next we have to create an edit form. This form is exactly the same as the create form. We can just about duplicate the show.html.erb to edit.html.erb. All we are going to do is change the title. Create a new file in /app/views/books/edit.html.erb and paste this: <% title "Editing #{@book.title}" %>

<% form_for(@book) do |form| %> <p> <%= form.label :title %><br/> <%= form.text_field :title %> </p>

&lt;p&gt;
  &lt;%= form.label :thoughts %&gt;&lt;br/&gt;
  &lt;%= form.text_area :thoughts %&gt;
&lt;/p&gt;

&lt;%= form.submit %&gt;

<% end %> Now from one of the book’s pages, click the edit link. You should see a familiar form: Notice how Rails filled in the inputs with the saved values? Nice huh. Go ahead and save some changes to a book. When you’re done you should see this: Now delete that book. You should get a confirmation dialog then be redirected back to /books. Add a link to create a new book on the index page. Open up /app/views/books/index.html.erb and add this to the bottom: <p> Admin actions: <%= link_to 'New Book', new_book_path %> </p> Now that we have CRUD functionality. We need to create our admin user. Fifty Five Maintaing user logins is a solved problem in Rails. You rarely have to write your own authentication system. We’re going to use the authlogic gem. Authlogic provides simple mechanics to authenticate users and store sessions. This is prefect for our app. We need an admin to login so he can create/edit/delete books. First let’s start by installing the authlogic gem. # add config.gem 'authlogic' in environment.rb bookshelf $ sudo rake gems:install Create a new model to hold the admins. Since our users are only admins, we’ll name the model Admin. For now the model only needs a login attribute. Generate the model using script/generate model: bookshelf $ ./script/generate model Admin login:string exists app/models/ exists test/unit/ exists test/fixtures/ create app/models/admin.rb create test/unit/admin_test.rb create test/fixtures/admins.yml exists db/migrate create db/migrate/20091204202129_create_admins.rb Now add authlogic specific columns to our admin model. Open up the migration you just created and paste this into it: class CreateAdmins < ActiveRecord::Migration def self.up create_table :admins do |t| t.string :login t.string :crypted_password, :null => false t.string :password_salt, :null => false t.string :persistence_token, :null => false t.timestamps end end

def self.down
  drop_table :admins
end

end Now migrate your database. bookshelf $ rake db:migrate == CreateAdmins: migrating =================================================== -- create_table(:admins) -> 0.0025s == CreateAdmins: migrated (0.0026s) ========================================== Now the admin model is created. Next we need to create an authlogic session for that admin. Authlogic includes a generator for this: bookshelf $ ./script/generate session admin_session exists app/models/ create app/models/admin_session.rb Next we need to create some routes for logging in and out. Open up routes.rb and add this line: map.resource :admin_session Now we need a controller to handle the logging in and out. Generate this controller using the generator: bookshelf $ ./script/generate controller AdminSessions exists app/controllers/ exists app/helpers/ create app/views/admin_sessions exists test/functional/ exists test/unit/helpers/ create app/controllers/admin_sessions_controller.rb create test/functional/admin_sessions_controller_test.rb create app/helpers/admin_sessions_helper.rb create test/unit/helpers/admin_sessions_helper_test.rb Now open up /app/controllers/admin_sessions_controller.rb and paste this into it: class AdminSessionsController < ApplicationController def new @admin_session = AdminSession.new end

def create
  @admin_session = AdminSession.new(params[:admin_session])
  if @admin_session.save
    flash[:notice] = &quot;Login successful!&quot;
    redirect_to books_path
  else
    render :action =&gt; :new
  end
end

def destroy
  current_admin_session.destroy
  flash[:notice] = &quot;Logout successful!&quot;
  redirect_to books_path
end

end Wow! It seems like we just did a lot, but we haven’t. We’ve just created 2 new models. One model to hold our admins, and the other to hold admin session information. Finally we created a controller to handle the logging in and out. Now we need a view to show a login form. Create a new file at /app/views/admin_sessions/new.html.erb and paste this into it: <% title 'Login' %>

<% form_for @admin_session, :url => admin_session_path do |f| %> <%= f.error_messages %> <p> <%= f.label :login %><br /> <%= f.text_field :login %> </p>

&lt;p&gt;
  &lt;%= f.label :password %&gt;&lt;br /&gt;
  &lt;%= f.password_field :password %&gt;
&lt;/p&gt;

&lt;%= f.submit &quot;Login&quot; %&gt;

<% end %> We’re almost done. We still need to tell our Admin model that it uses authlogic and add some logic to our application controller to maintain session information. All controller inherit from application_controller, so it’s a good way to share methods between controllers. Open up /app/controllers/application_controller.rb and paste this: class ApplicationController < ActionController::Base helper :all # include all helpers, all the time protect_from_forgery # See ActionController::RequestForgeryProtection for details

# Scrub sensitive parameters from your log
# filter_parameter_logging :password

filter_parameter_logging :password, :password_confirmation
helper_method :current_admin_session, :current_admin

private
def current_admin_session
  return @current_admin_session if defined?(@current_admin_session)
  @current_admin_session = AdminSession.find
end

def current_admin
  return @current_admin if defined?(@current_admin)
  @current_admin = current_admin_session &amp;&amp; current_admin_session.user
end

end Now in /app/models/admin.rb add this line inside the class: # /app/models/admin.rb acts_as_authentic We’re finally ready to do some logging in and out. All of the stuff we did was almost purely from the authlogic documentation examples. This is a standard setup for many applications. If you want to find out more about how authlogic works you can here. Here’s a run down of what we did.Install the authlogic gemCreate an Admin model to hold the basic information like login/passwordAdd authlogic specific columns to the Admin tableGenerated an authlogic admin sessionCreated routes for logging in and outGenerated an AdminSession controller to do all the workCreated a view that shows a login formAdded methods to ApplicationController for persisting sessionsTold the Admin model that it uses authlogic It’s time to create the admin account. Our application is simple and only has one admin. Since we only have one admin, we can easily use the console. Since we’ll need to recreate that user later when we deploy, it doesn’t make sense to do the same thing twice. Rails now has a functionality for seeding the database. This is perfect for creating the initial records. There is a file /db/seeds.rb where you can write ruby code to create your initial models. Then you can run this file through rake db:seed. In order to create our admin model we’ll need a login, password, and password confirmation. Open up /db/seeds.rb and paste this. Fill in the login with the name you want. Admin.create! :login => 'Adam', :password => 'nettuts', :password_confirmation => 'nettuts' We use the create! method because it will throw an exception if the record can’t be saved. Go ahead and run the rake task to seed the database: bookshelf $ rake db:seed Now we should be able to login. Restart the server to get the new routes. Head to /admin_session/new. You should see: Go ahead and fill it in and now you should be logged in! Now that admins can login, we can give them access to the new/edit/delete functionality. Rails has these awesome things called filters. Filters are things you can do at points in the request lifecycle. The most popular filter is a before_filter. This filter gets executed before an action in the controller. We can create a before filter in the books controller that checks to see if we have a logged in admin. The filter will redirect users who aren’t logged in, therefore preventing unauthorized access. Open up books_controller.rb and add these lines: # first line inside the class: before_filter :login_required, :except => [:index, :show]

# after all the actions private def login_required unless current_admin flash[:error] = 'Only logged in admins an access this page.' redirect_to books_path end end Now we need to update our views to show the admin links only if there’s an admin logged in. That’s easy enough. All we need to do is wrap it in an if. # show.html.erb <% if current_admin %> <p> Admin Actions: <%= link_to 'Edit', edit_book_path(@book) %> | <%= link_to 'Delete', book_path(@book), :method => :delete, :confirm => "Are you sure?" %> </p> <% end %>

# index.html.erb <% if current_admin %> <p> Admin actions: <%= link_to 'New Book', new_book_path %> </p> <% end %> We still need to add a login/logout link. That should go on every page. An easy way to put something on every page is add it to the layout. # /app/views/layouts/application.erb <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html> <head> <title><%= h(yield(:title) || "Untitled") %></title> <%= stylesheet_link_tag 'application' %> <%= yield(:head) %> </head> <body> <div id="container"> <%- flash.each do |name, msg| -%> <%= content_tag :div, msg, :id => "flash_#{name}" %> <%- end -%>

    &lt;%- if show_title? -%&gt;
      &lt;h1&gt;&lt;%=h yield(:title) %&gt;&lt;/h1&gt;
    &lt;%- end -%&gt;

    &lt;%= yield %&gt;

    &lt;% if current_admin %&gt;
      &lt;p&gt;&lt;%= link_to &#39;Logout&#39;, admin_session_path(current_admin_session), :method =&gt; :delete %&gt;&lt;/p&gt;
    &lt;% else %&gt;
      &lt;p&gt;&lt;%= link_to &#39;Login&#39;, new_admin_session_path %&gt;&lt;/p&gt;
    &lt;% end %&gt;
  &lt;/div&gt;
&lt;/body&gt;

</html> Now you should have login/logout links on pages depending if your logged in and logged out. Go ahead and click the through the app. Try access the new book page after you’ve logged out. You should see an error message. Click through the app. You should be able to login and out, and edit/create/delete books. Time for the final step. Let’s add some formatting to your thoughts and user comments. Rails has a helper method that will change new lines to line breaks and that sorta thing. Add that show.html.erb: # <p><%= @book.thoughts %></p> becomes <%= simple_format @book.thoughts %>

# do the same thing for comments # <p><strong><%=h(comment.author) %></strong>: <%=h comment.text %> becomes <p><strong><%=h(comment.author) %></strong>:</p> <%= simple_format comment.text %> It doesn’t make sense to put the thoughts in the index, so let’s replace that with a preview instead of the entire text. # index.html.erb # <p><%= book.thoughts %></p> becomes <%= simple_format(truncate(book.thoughts, 100)) %> Now our final index page should look like this: Finally we need to set up a route for our root page. Open up routes.rb and add this line: map.root :controller => 'books', :action => 'index' Now when you go to / you’ll see the book listing. Sixty Now we are going to deploy this app in a few steps. You don’t need your own server or anything like that. All you need is an account on Heroku. Heroku is a cloud Rails hosting service. If you have a small app, you can use their service for free. Once you’ve signed up for an account, install the heroku gem: $ sudo gem install heroku Heroku works with git. Git is a distributed source control management system. In order to deploy to heroku all you need to do is create your app then push your code to it’s server. If you haven’t already install git, instructions can be found here. Once you have heroku and git installed you are ready to deploy. First thing we need to do is create a new git repo out of your project: bookshelf $ git init Initialized empty Git repository in /Users/adam/Code/bookshelf/.git/ It’s time to do some preparation for heroku deployment. In order to get your application’s gems installed, create a .gems file in the root project directory. Each line has the name of the gem on it. When you push your code to heroku it will read the .gems file and install the gems for you. So create a .gems file and paste this into it: forgery will_paginate authlogic There is a problem with authlogic on heroku, so we need to create an initializer to require the gem for us. Create a new file in /config/initializers/authlogic.rb and put this line in there: require 'authlogic' Now we should be ready to deploy. First thing you’re going to do is run heroku create. This will create a new heroku app for you. If you’re a first time user, it will guide you through the setup process. bookshelf $ heroku create Git remote heroku added No we are ready to deploy. Here are the stepsAdd all files in the project to a commitCommit the filesPush are code to herokuMigrate the database on herokuSeed the database on herokuRestart the heroku serverOpen your running application bookshelf $ git add -A bookshelf $ git commit -m 'Initial commit' bookshelf $ git push heroku master bookshelf $ heroku rake db:migrate bookshelf $ heroku rake db:seed bookshelf $ heroku restart bookshelf $ heroku open Here is the finally app running on the world wide web: Hit the Brakes We’ve covered a lot of ground in this article, so where do we go from here? There are few things we didn’t do in this app. We didn’t add any validations to the models. We didn’t use partials. We didn’t do any administration for the comments. These are things you should look into next. Here are some links to help you with the next steps. Completed Source CodeConfused about the form parts? Read thisConfused about routes? Read thisConfused about heroku? Read thisConfused about associations? Read thisConfused about authlogic? Read this Links to gems used in this project. Forgery Authlogic Will PaginateFollow us on Twitter, or subscribe to the Nettuts+ RSS Feed for the best web development tutorials on the web. Ready Ready to take your skills to the next level, and start profiting from your scripts and components? Check out our sister marketplace, CodeCanyon.

]]>
Mon, 04 Jan 2010 01:30:26 -0800 http://www.federicobond.com.ar/items/view/463/zero-to-sixty-creating-and-deploying-a-rails-app-in-under-an-hour
Zero-to-Sixty: Creating and Deploying a Rails App in Under an Hour http://www.federicobond.com.ar/items/view/413/zero-to-sixty-creating-and-deploying-a-rails-app-in-under-an-hour

Give me an hour of your time, and I’ll take you on a fly by of the Ruby on Rails framework. We’ll create controllers, models, views, add admin logins, and deploy using Heroku’s service in under an hour! In this article we will create a simple bookshelf application where you can add books and write thoughts about them. Then we will deploy the application in just a few minutes. So buckle up because this article moves fast! This article assumes that you may know what Ruby on Rails is, but not exactly how it works. This article does not describe in-depth how each step works, but describe what we need to do, then the code to do that. The reader will get a jet-speed flyover overview of creating a simple Rails application and deploying it on Heroku. Zero Ruby on Rails is a full stack MVC web application framework. Full stack means you get everything: a simple web server you can use to test your apps, a database layer, testing framework, and an MVC based design. MVC stands for Model-View-Controller. A model is something that stores information. In this case models are stored in a database. Rails supports MySQL, PostgreSQL, or SQLite. Each model has it’s own class and table. Let’s say we want to model a “game.” A game will have things like number of players, a start time, and end time, teams playing, and a winner. Each of these attributes becomes a column in the “games” table. Table names are lowercase, underscored, and pluralized. Then our model corresponds to class named Game. In Rails you create models through migrations and generators. A migration describes how to add/remove columns and tables from the database. A controller is the manager. It takes information and does some logic like CRUD, or maybe import some stuff from a file, add/remove permissions–you name it a controller can do it. Controllers are the part of your app that does. How do we call controllers? Rails uses routes. A route is a formatted url that is tied to a controller action with a set of parameters. Going back to our Game model, we need a controller for functionality. At some point we’ll to need to list all the games in the system. A basic REST url for this route looks like “/games” How does Rails know what controller to look for and what action to call? It looks at your routes.rb file. You may have a route that looks like this “GET /makes {:name => ‘games’, :action => ‘index’”}. This means we are going to look at the GamesController and it’s index method. Just like models, class names are CamelCase and file names are underscored. So our GamesController would be stored in /app/controllers/games_controller.rb. When we are done with our logic, the controller is going to render a view. A view is the easiest part to understand. It’s what you see. It’s the HTML you generate to show the user what they need. Views are ERB templates. ERB stands for Embedded Ruby. You use ERB similar to how you embed php into a document. If you want to insert an instance variable @game.time into some html write <%= @game.time %> Ten First install Rails. Installing Rails is very easy depending on your platform. If you are on a Linux/OSX, it’s no problem. Windows is more complicated and I have no experience with it. This section will give you a brief overview of installing Rails through RubyGems, the Ruby package manager. A gem is a bundle of ruby code in a package that can be used in your programs. For UNIX based system we need to install RubyGems, then install the Rails gem. This process will go something like this: # ubuntu sudo apt-get install rubygems # fedora sudo yum install rubygems # gentoo sudo emerge rubygems # OSX sudo port install rubygems

# after you have rubygems installed
sudo gem install gemcutter # ruby gem hosting service
sudo gem tumble
sudo gem install Rails

Here are some links to help you through the setup process Instant Rails, like instant LAMP but for Rails Ubuntu Community Docs about Ruby on Rails Getting Rails running on Windows Snow Leopard on Rails by a guy I know The mandatory google link Once you can run the “rails” command you are ready for the next step. Fifteen Hopefully by now you’ve got RubyGems and Rails installed. Now it’s time to install some database support before we get started. Rails has support for all the popular DB’s, but for this example we are going to use SQLite because it’s lightweight.. Depending on your platform (again) installing sqlite support can be easy or painful. It can be a pain since the gem has to be built against C extensions, which means you’ll need to have the sqlite3 package installed on your system. Again the process will go something like this: # ubuntu sudo apt-get install salite3 sqlite3-devel # fedora sudo yum install sqlite3 sqlite3-devel # OSX sudo port install sqlite3

# then once you have the sqlite3 package installed
sudo gem install sqlite3-ruby

Read the pervious links if you’re having problems with these steps. They describe installing sqlite as well. Twenty It is now time to generate our app. The Rails command will do all of this for us. All we need to do is be in a directory and run it like so: $ cd ~/projects $ Rails bookshelf #this will create a new directory named bookshelf that holds our app $ cd bookshelf It is important to note that the Rails default is an SQLite based app. You may be thinking, well what if I don’t want that? The rails command is a generator. All it does it copy stored files into a new directory. By default it will create sqlite3 databases in /bookshelf/db/development.sqlite3, /bookshelf/db/production.sqlite3, and /bookshelf/db/testing.sqlite3. Database information is stored in /bookshelf/config/database.yml. You will not need to edit this file for this project since it contains default information for an sqlite setup. It should look like this: # SQLite version 3.x # gem install sqlite3-ruby (not necessary on OS X Leopard) development: adapter: sqlite3 database: db/development.sqlite3 pool: 5 timeout: 5000

# Warning: The database defined as "test" will be erased and
# re-generated from your development database when you run "rake".
# Do not set this db to the same as development or production.
test:
  adapter: sqlite3
  database: db/test.sqlite3
  pool: 5
  timeout: 5000

production:
  adapter: sqlite3
  database: db/production.sqlite3
  pool: 5
  timeout: 5000

The file is pretty self explanatory. You will notice that there are different environments assigned. Rails has three modes by default: Development, Testing, and Production. Each can have different settings and databases so you can keep them separate and customized. Development is the default environment. At this point we can start our app to make sure it’s working. You can see there is a directory called /script. This directory contains ruby scripts for interacting with our applications. Some important ones are /script/console, and script/server. We will use the script/server command to start a simple server for our application. bookshelf $ ./script/server # then you should see something like this. Rails will start a different server depending on your platform, but it should look something like this: => Booting Mongrel => Rails 2.3.5 application starting on http://0.0.0.0:3000 => Call with -d to detach => Ctrl-C to shutdown server Time to go to our application. Point your browser to “http://localhost:3000″ and you should see this splash page: You’re riding on Rails. Now the code working on a basic level it’s time to delete the splash page and get started with some code. bookshelf $ rm /public/index.html Twenty Five Our application is going to need data. Remember what this means? It means models. Great, but how we generate a model? Rails comes with some generators to common tasks. The generator is in /script/generate. The generate will create our model.rb file along with a migration to add the table to the database. A migration is file that contains commands to add/drop tables, or alter/add/remove columns from tables. Migrations are stored in order and are executed in sequence to create your database tables. You run your migrations (and various other commands) with “rake”. Rake is a ruby code runner. Before we get any further, let’s start by defining some basic information we are going to need to track for your books. A book has these attributes:Title : StringThoughts : Text That is enough to get started with the basic application. Start by generating a model with these fields using the model generator: bookshelf $ ./script/generate model Book title:string thoughts:text # notice how the attributes/types are passed to the generator. This will automatically create a migration for these attributes # These are optional. If you leave them out, the generator will create an empty migration. exists app/models/ exists test/unit/ exists test/fixtures/ create app/models/book.rb create test/unit/book_test.rb create test/fixtures/books.yml create db/migrate create db/migrate/20091202052507_create_books.rb # The generator went ahead and generated all the files we need to get our model up and running. We need to pay the most attention to these files: # app/models/book.rb # where our code resides # db/migrate/20091202052507_create_books.rb # code to create our books table. Open up the migration file: class CreateBooks < ActiveRecord::Migration def self.up create_table :books do |t| t.string :title t.text :thoughts

    t.timestamps
  end
end

def self.down
  drop_table :books
end

end Notice the create_table :books do block. This is where columns are created. An id primary key is created automatically. t.timestamps add columns for created_at and updated_at. Now run the migration using the rake task db:migrate. db:migrate will applies pending migrations: bookshelf $ rake db:migrate == CreateBooks: migrating ==================================================== -- create_table(:books) -> 0.0037s == CreateBooks: migrated (0.0038s) =========================================== Cool, now we have a table, let’s create a dummy book just for kicks in the console. The Rails console uses IRB (interactive ruby) and loads all classes for your project. IE you have access to all your models. Open the console like this: bookshelf $ ./script/console >> # let's create a new model. You can specify a hash of assignments in the constructor to assign values like this: >> book = Book.new :title => 'Rails is awesome!' , :thoughts => 'Some sentence from a super long paragraph' => #<Book id: nil, title: "Rails is awesome!", thoughts: "Some sentence from a super long paragraph", created_at: nil, updated_at: nil> # and ruby will display it back >> book.save => true # now are book is saved in the database. We can query it like this: >> Book.all # find all books and return them in an array => [#<Book id: 1, title: "Rails is awesome!", thoughts: "Some sentence from a super long paragraph", created_at: "2009-12-02 06:05:38", updated_at: "2009-12-02 06:05:38">] >> exit # now that our model is saved, let's exit the console. Now that we can create books, we need some way to show them to the user Thirty Remember controllers? We need a controller to display all the books in the system. This scenario corresponds to the index action in our BooksController (books_controller.rb) which we don’t have yet. Just like generating models, use a generator: bookshelf $ ./script/generate controller Books exists app/controllers/ exists app/helpers/ create app/views/books exists test/functional/ create test/unit/helpers/ create app/controllers/books_controller.rb create test/functional/books_controller_test.rb create app/helpers/books_helper.rb create test/unit/helpers/books_helper_test.rb # notice Rails created the file app/controllers/books_controller.rb? This is where we are going to define our actions or methods for the BooksController class So we need to define an action that finds and displays all books. How did we find all the books? Earlier we used Book.all. Our strategy is use Book.all and assign it to an instance variable. Why an instance variable? We assign instance variables because views are rendered with the controllers binding. You’re probably thinking bindings and instance variables…what’s going on? Views have access to variables defined in actions but only instance variables. Why, because instance variables are scoped to the object and not the action. Let’s see some code: class BooksController < ApplicationController # notice we've defined a method called index for a BooksController instance. We tie this together with routes def index @books = Book.all # instance variables are prefixed with an @. If we said books = Book.all, we wouldn't be able to access books in the template end end Now the controller can find all the books. But how do we tie this to a url? We have to create some routes. Rails comes with some handy functions for generating RESTful routes (another Rails design principle). This will generate urls like /makes and /makes/1 combined with HTTP verbs to determine what method to call in our controller. Use map.resources to generate all these routes for us. Open up /config/routes.rb and change it to this: ActionController::Routing::Routes.draw do |map| map.resources :books end Routes.rb can look arcane to new users. Luckily there is a way to decipher this mess. There is routes rake task to display all your routing information. Run that now and take a peek inside: bookshelf $ rake routes books GET /books(.:format) {:controller=>"books", :action=>"index"} POST /books(.:format) {:controller=>"books", :action=>"create"} new_book GET /books/new(.:format) {:controller=>"books", :action=>"new"} edit_book GET /books/:id/edit(.:format) {:controller=>"books", :action=>"edit"} book GET /books/:id(.:format) {:controller=>"books", :action=>"show"} PUT /books/:id(.:format) {:controller=>"books", :action=>"update"} DELETE /books/:id(.:format) {:controller=>"books", :action=>"destroy"} # as you can see this command can display a lot of information. On the left column we have a helper to generate a url, then the HTTP verb associated with the url, then the url, and finally the controller and action to call. # for example GET /books will call BooksController#index or # GET /books/1 will call BooksController#show # the url helpers are very important but we'll get to them later. For now know that we are going to create a /books page to list all books Now we need to create a template to display all our books. Create a new file called /app/views/books/index.html.erb and paste this: <% for book in @books do %> <h2><%=h book.title %></h2> <p><%= book.thoughts %></p> <% end %> This simple view loops over all @books and displays some HTML for each book. Notice a subtle difference. <%= is used when we need to output some text. <% is used when we aren’t. If you don’t follow this rule, you’ll get an exception. Also notice the h before book.title. h is a method that escapes HTML entities. If you’re not familiar with ruby, you can leave off ()’s on method calls if they’re not needed. h text translates to: h(text). Time to run the server and see what we’ve got. Start the server, then go to http://localhost/books. bookshelf $ ./script/server If all goes according to plan you should see some basic HTML. Thirty Five We have one book in our system, but we need some more books to play within our application. There are a few ways to go about doing this. I like using a gem named ‘forgery’. Forgery can create random strings like names, or lorem ipsum stuff. We are going to set a gem dependency in our app, install the gem, then use it to create a rake task to populate our data. Step 1: open up /config/environment.rb and add this line: config.gem 'forgery'

# now let's tell Rails to install all gems dependencies for this project # gem install gemcutter # if you haven't already # gem tumble # again, if you haven't already bookshelf $ sudo rake gems:install Now we are going to use the Forgery classes to create some fake data. The Forgery rdoc can be found here. We’ll use the LoremIpsumForgery to create some basic data. We can define our own rake tasks for our applications by creating a .rake file in /lib/tasks. So create a new file /lib/tasks/populate.rake: begin namespace :db do desc "Populate the development database with some fake data" task :populate => :environment do 5.times do Book.create! :title => Forgery::LoremIpsum.sentence, :thoughts => Forgery::LoremIpsum.paragraphs(3) end end end rescue LoadError puts "Please run: sudo gem install forgery" end This rake task will create 5 fake books. Notice I added a begin/rescue. When you run a rake task it looks at all possible tasks in the rake initialization. If you were to run a rake task before you installed the gem, your task would blow up. Wrapping it in an begin/rescue will allow other rake tasks to run. Run the task to populate our db: bookshelf $ rake db:populate bookshelf $ ./script/console # let's enter the console to verify it all worked >> Book.all => [#<Book id: 1, title: "Rails is awesome!", thoughts: "Some sentence from a super long paragraph", created_at: "2009-12-02 06:05:38", updated_at: "2009-12-02 06:05:38">, #<Book id: 2, title: "Lorem ipsum dolor sit amet, consectetuer adipiscing...", thoughts: "Lorem ipsum dolor sit amet, consectetuer adipiscing...", created_at: "2009-12-02 07:28:05", updated_at: "2009-12-02 07:28:05">, #<Book id: 3, title: "Lorem ipsum dolor sit amet, consectetuer adipiscing...", thoughts: "Lorem ipsum dolor sit amet, consectetuer adipiscing...", created_at: "2009-12-02 07:28:05", updated_at: "2009-12-02 07:28:05">, #<Book id: 4, title: "Lorem ipsum dolor sit amet, consectetuer adipiscing...", thoughts: "Lorem ipsum dolor sit amet, consectetuer adipiscing...", created_at: "2009-12-02 07:28:05", updated_at: "2009-12-02 07:28:05">, #<Book id: 5, title: "Lorem ipsum dolor sit amet, consectetuer adipiscing...", thoughts: "Lorem ipsum dolor sit amet, consectetuer adipiscing...", created_at: "2009-12-02 07:28:05", updated_at: "2009-12-02 07:28:05">, #<Book id: 6, title: "Lorem ipsum dolor sit amet, consectetuer adipiscing...", thoughts: "Lorem ipsum dolor sit amet, consectetuer adipiscing...", created_at: "2009-12-02 07:28:05", updated_at: "2009-12-02 07:28:05">] >> exit Start the server again and head back to the /books pages. You should see: Now we have a listing of more than 1 book. What if we have a lot of books? We will need to paginate the results at some point. There’s another gem we’ll use for this. The gem is ‘will_paginate.’ Following the same steps as before, let’s add a gem dependency for ‘will_paginate’ and rake gems:install: # in environment.rb config.gem 'will_paginate' # from terminal bookshelf $ sudo rake gems:install # then let's add more books to our db bookshelf $ rake db:populate # run this a few times to get a large sample, or change the number in rake file Head back to your /books page and you should be bombarded by books at this point. it’s time to add pagination. Pagination works at two levels. The controller decides what books should be in @books, and the view should display the pagination links. The will_paginate helper makes this very easy. We’ll use the .paginate method and the will_paginate view helper to render page links. All it takes is two lines of code. # books_controller.rb, change the previous line to: @books = Books.paginate :page => params[:page], :per_page => 10

# index.html.erb, add this line after the loop: <%= will_paginate @books %> Head back to your /makes page and you should see some pagination links (given you have more than 10 books) Sweet! We are movin’ through this app. it’s time to spruce up our page a little bit. One key Rails principle is DRY (Do Not Repeat Yourself). We could work through the exercise of doing some basic CSS to get a page looking ok, or we could keep things DRY and use some code to do it for us. We are going to use Ryan Bate’s nifty-generators gem to generate a layout for the site. A layout is a template your views can fill in. For example we can use a layout to determine the over all structure of a page, then define where the views fill it in. Since this isn’t a project dependency, we don’t have to add it to environment.rb. We can just install it regularly. # console $ sudo gem install nifty-generators Run the generator to create a layout file and stylesheets. $ ./script/generate nifty_layout exists app/views/layouts exists public/stylesheets exists app/helpers create app/views/layouts/application.html.erb # this is our layout file create public/stylesheets/application.css # css file that styles the layout create app/helpers/layout_helper.rb # view helpers needed in the generator let’s take a look at the application.html.erb file and see what’s inside: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html> <head> <title><%= h(yield(:title) || "Untitled") %></title> <%= stylesheet_link_tag 'application' %> <%= yield(:head) %> </head> <body> <div id="container"> <%- flash.each do |name, msg| -%> <%= content_tag :div, msg, :id => "flash_#{name}" %> <%- end -%>

    &lt;%- if show_title? -%&gt;
      &lt;h1&gt;&lt;%=h yield(:title) %&gt;&lt;/h1&gt;
    &lt;%- end -%&gt;

    &lt;%= yield %&gt;
  &lt;/div&gt;
&lt;/body&gt;

</html> See those yields? That’s where a view fills in the layout. The last yield has no argument. Default content goes in there. Yields with an argument must have content defined using content_for. Fix up index.html.erb view to go with the new layout: <% title 'My Books' %>

<% for book in @books do %> <h2><%=h book.title %></h2> <p><%= book.thoughts %></p> <% end %>

<%= will_paginate @books %> All we did was add call the title method which sets the title for a page. The title helper calls content_for :title and sets it to the argument. Our view fills in the last yield. Check out the results! Forty Now that our application is looking better, let’s add some interaction. In typical Web 2.0 style we are going to allow users to comment on our content, but we are not going to require the user to register. To do this we need to create new model called Comment. A comment is going to have some text, an author, and an associated Book. How do we link these two models together? Associations. Rails provides these associations: belongs_to, has_many, has_one, and has_and_belongs_to many. It should be easy to see that a book has many comments, and a comment belongs_to a book. So we’ll use a generator to create the comment model and migration: $ ./script/generate model Comment text:text author:string exists app/models/ exists test/unit/ exists test/fixtures/ create app/models/comment.rb create test/unit/comment_test.rb create test/fixtures/comments.yml exists db/migrate create db/migrate/20091202081421_create_comments.rb Astute readers will notice that this migration is lacking the foreign key column. We’ll have to add that ourself. Open up your create_comments.rb migration: class CreateComments < ActiveRecord::Migration def self.up create_table :comments do |t| t.text :text t.string :author t.belongs_to :book # creates a new integer column named book_id t.timestamps end end

def self.down
  drop_table :comments
end

end

# now migrate your database $ rake db:migrate rake db:migrate (in /Users/adam/Code/bookshelf) == CreateComments: migrating ================================================= -- create_table(:comments) -> 0.0021s == CreateComments: migrated (0.0022s) ======================================== Now it’s time to associate our models using the Rails associations. We’ll call the method inside the model’s class body. Rails uses metaprogramming to generate the methods needed to make our association work. We’ll edit our comment.rb and book.rb files: # book.rb class Book < ActiveRecord::Base has_many :comments end

# comment.rb class Comment < ActiveRecord::Base belongs_to :book end Now Book instances have a method .comments with returns an array of all the comments. Comment instances have a method called .book that return the associated book. We can use the << operator to add things to arrays. let’s see how it works in the console: $ ./script/console >> book = Book.find(1) => #<Book id: 1, title: "Rails is awesome!", thoughts: "Some sentence from a super long paragraph", created_at: "2009-12-02 06:05:38", updated_at: "2009-12-02 06:05:38"> >> comment = Comment.new :text => "This is an comment", :author => "Adam" => #<Comment id: nil, text: "This is an comment", author: "Adam", book_id: nil, created_at: nil, updated_at: nil> >> book.comments << comment => [#<Comment id: 1, text: "This is an comment", author: "Adam", book_id: 1, created_at: "2009-12-02 08:25:47", updated_at: "2009-12-02 08:25:47">] >> book.save => true >> book.comments => [#<Comment id: 1, text: "This is an comment", author: "Adam", book_id: 1, created_at: "2009-12-02 08:25:47", updated_at: "2009-12-02 08:25:47">] >> comment.book => #<Book id: 1, title: "Rails is awesome!", thoughts: "Some sentence from a super long paragraph", created_at: "2009-12-02 06:05:38", updated_at: "2009-12-02 06:05:38"> >> exit In the console session I found one of the existing books, then created a new comment. Next I added it to the book.comments. Then I save book. The book must be saved for the association to be stored. What do we need to do next? We need to create a page where the user can view a book and all it comments. That page should also have a form where the user can add their comment. Create a new action in the books controller to show the details for a specified book. The book will be found by id. Pop into books_controller.rb and add this: def show @book = Book.find params[:id] end Make a new template for this action at /app/views/books/show.html.erb and paste this: <% title @book.title %>

<h2><%=link_to(h(@book.title), book_path(@book)) %></h2> <p><%= @book.thoughts %></p> Now let’s add some links for the index actions to link the show action: # update index.html.erb contents to: <% title 'My Books' %>

<% for book in @books do %> <h2><%=link_to(h(book.title), book_path(book)) %></h2> <p><%= book.thoughts %></p> <% end %>

<%= will_paginate @books %> Remember our url helpers from rake routes? We’re using book_path to generate a url to our controller’s show actions. If you don’t remember check rake routes again. link_to is a helper to generate a link tag. Now let’s fire up our server and click through the app. Now you should have some ugly blue links. Click on your book title and it should go to /books/:id aka BooksController#show: Time to display some comments. Remember that console session we did a little bit back? One of our books has some comments. let’s update our controller to find the comments and our show.html.erb to display them. # books_controller.rb def show @book = Book.find(params[:id]) @comments = @book.comments end

# show.html.erb <% title @book.title %>

<h2><%=link_to(h(@book.title), book_path(@book)) %></h2> <p><%= @book.thoughts %></p>

<% if @comments %> <h3>Comments</h3>

&lt;% for comment in @comments do %&gt;
  &lt;p&gt;&lt;strong&gt;&lt;%=h(comment.author) %&gt;&lt;/strong&gt;: &lt;%=h comment.text %&gt;
&lt;% end %&gt;

<% end %> So we assign @comments in the controller to be all the book’s comments, then do a loop in the view to display them. Head over to /books/1 (1 came from Book.find(1) in the console session). Check this out: Now we need the form to post a new comment. In order to do this we are going to need two things. 1, A comments controller to save the comment, and 2 a route to that action. let’s tackle #1 first. bookshelf $ ./script/generate controller Comments Create action that instantiates a new comment, sets it’s attributes (text/author) from the submitted form data, and save it. class CommentsController < ApplicationController def create book = Book.find params[:book_id] comment = book.comments.new params[:comment] comment.save flash[:notice] = 'Comment saved' redirect_to book_path(book) end end First the code finds the book, then creates a new comment for it based of the form data, save it, set a message, then redirect back to that book’s page. params holds a hash of all GET/POST data with a request. Now we need to create a route for our controller actions. Open up routes.rb: ActionController::Routing::Routes.draw do |map| map.resources :books do |book| book.resources :comments, nly => :create end end

bookshelf $ rake routes # We needed to add a route to create a new comment for a book. We need to know what book we are creating a comment for, so we need a book_id in the route. Look at the book_comment line. # book_comment is tied to our CommentsController#create book_comments POST /books/:book_id/comments(.:format) {:controller=>"comments", :action=>"create"} books GET /books(.:format) {:controller=>"books", :action=>"index"} POST /books(.:format) {:controller=>"books", :action=>"create"} new_book GET /books/new(.:format) {:controller=>"books", :action=>"new"} edit_book GET /books/:id/edit(.:format) {:controller=>"books", :action=>"edit"} book GET /books/:id(.:format) {:controller=>"books", :action=>"show"} PUT /books/:id(.:format) {:controller=>"books", :action=>"update"} DELETE /books/:id(.:format) {:controller=>"books", :action=>"destroy"} # every time you modify routes.rb you'll need to restart the server # kill the server process you have running with ^c (ctrl + c) and start it again Head back to the /books page and make sure nothing has blown up. Everything should be fine and dandy. Now for constructing the form. We need a form that submit’s post data to /book/:book_id/comments. Luckily Rails has the perfect helper for this: form_for. form_for takes some models and generates a route for them. We pass form_for a block to create HTML for form inputs. Go a head and paste this into the bottom of your show.html.erb: <h3>Post Your Comment</h3> <% form_for([@book, Comment.new]) do |form| %> <p><%= form.label :author %></p> <p><%= form.text_field :author %></p>

&lt;p&gt;&lt;%= form.label :text, &#39;Comment&#39; %&gt;&lt;/p&gt;
&lt;p&gt;&lt;%= form.text_area :text %&gt;&lt;/p&gt;

&lt;%= form.submit &#39;Save&#39; %&gt;

<% end %> We call form_for to create a new form our book’s comment, then use the text_field/text_area to create new inputs that will be assigned to those attributes. At this point we can go ahead and make a comment. Fill in the form and viola you now have comments! See that green thing? That’s the flash. The flash is a way to store messages between actions. it’s perfect for storing little messages like this. But what do we do if get a book with a lot of comments? We paginate them just like did before. So let’s make some changes to the controller and view: # books_controller.rb def show @book = Book.find(params[:id]) @comments = @book.comments.paginate :page => params[:page], :per_page => 10, rder => 'created_at ASC' end

# show.html.erb

<% title @book.title %>

<h2><%=link_to(h(@book.title), book_path(@book)) %></h2> <p><%= @book.thoughts %></p>

<% if @comments %> <h3>Comments</h3>

&lt;% for comment in @comments do %&gt;
  &lt;p&gt;&lt;strong&gt;&lt;%=h(comment.author) %&gt;&lt;/strong&gt;: &lt;%=h comment.text %&gt;
&lt;% end %&gt;

&lt;%= will_paginate @comments %&gt;

<% end %>

<h3>Post Your Comment</h3> <% form_for([@book, Comment.new]) do |form| %> <p><%= form.label :author %></p> <p><%= form.text_field :author %></p>

&lt;p&gt;&lt;%= form.label :text, &#39;Comment&#39; %&gt;&lt;/p&gt;
&lt;p&gt;&lt;%= form.text_area :text %&gt;&lt;/p&gt;

&lt;%= form.submit &#39;Save&#39; %&gt;

<% end %> Start commenting on your books and you should see some pagination. Now people can comment, and everything is paginated but we’re missing something. We have no web interface for creating books. We need to create a form for that. Also we are the admin so only I should be allowed to create books. This means we need to create a user, login, and check to see if they can do an action. Fifty Now we are going to implement CRUD functionality for admins. First we’ll implement actions to create, edit, and delete books. Then we’ll create an admin login. Finally we’ll make sure only admins can do those actions. Creating a new books requires two new actions. One action that renders a form for a new book. This action is named ‘new’. The second is named ‘create.’ This action takes the form parameters and saves them in the database. Open up your books_controller.rb and add these actions: def new @book = Book.new end

def create @book = Book.new params[:book] if @book.save flash[:notice] = "#{@book.title} saved." redirect_to @book else render :new end end We also need a new view that shows a form. Create a new file /apps/views/books/new.html.erb and paste this: <% form_for(@book) do |form| %> <p> <%= form.label :title %><br/> <%= form.text_field :title %> </p>

&lt;p&gt;
  &lt;%= form.label :thoughts %&gt;&lt;br/&gt;
  &lt;%= form.text_area :thoughts %&gt;
&lt;/p&gt;

&lt;%= form.submit %&gt;

<% end %> Now we’re ready to create a new book. Point your browser to /books/new and you should see this form. Go a head and create a new book. After you fill in your form you should see your new book. let’s get rid of the double header in /app/views/books/show.html.erb and add some links to actions an admin can do on that book. Open up that file and set it’s contents to: <% title @book.title %>

<p><%= @book.thoughts %></p>

<% if @comments %> <h3>Comments</h3>

&lt;% for comment in @comments do %&gt;
  &lt;p&gt;&lt;strong&gt;&lt;%=h(comment.author) %&gt;&lt;/strong&gt;: &lt;%=h comment.text %&gt;
&lt;% end %&gt;

&lt;%= will_paginate @comments %&gt;

<% end %>

<h3>Post Your Comment</h3> <% form_for([@book, Comment.new]) do |form| %> <p><%= form.label :author %></p> <p><%= form.text_field :author %></p>

&lt;p&gt;&lt;%= form.label :text, &#39;Comment&#39; %&gt;&lt;/p&gt;
&lt;p&gt;&lt;%= form.text_area :text %&gt;&lt;/p&gt;

&lt;%= form.submit &#39;Save&#39; %&gt;

<% end %>

<p> Admin Actions: <%= link_to 'Edit', edit_book_path(@book) %> | <%= link_to 'Delete', book_path(@book), :method => :delete, :confirm => "Are you sure?" %> </p> Head over to a book’s page and you should see: Now that we have some links to edit and delete, you can implement them. Editing a book works just about the same as creating a new one. We need an action that shows an edit form, and one to save the changes. Delete is just one action that deletes the record from the database. Open up books_controller.rb and add these actions: def edit @book = Book.find params[:id] end

def update @book = Book.find params[:id] if @book.update_attributes(params[:book]) flash[:notice] = "#{@book.title} saved." redirect_to @book else render :edit end end

def destroy book = Book.find params[:id] book.destroy flash[:notice] = "#{book.title} deleted." redirect_to books_path end The edit actions finds the requested book from the id in the url. The update action finds the book from the id and uses the update_attributes method to set the new values from the form values. Delete finds the book by id and deletes it. Then it redirects you back to the books listing. Next we have to create an edit form. This form is exactly the same as the create form. We can just about duplicate the show.html.erb to edit.html.erb. All we are going to do is change the title. Create a new file in /app/views/books/edit.html.erb and paste this: <% title "Editing #{@book.title}" %>

<% form_for(@book) do |form| %> <p> <%= form.label :title %><br/> <%= form.text_field :title %> </p>

&lt;p&gt;
  &lt;%= form.label :thoughts %&gt;&lt;br/&gt;
  &lt;%= form.text_area :thoughts %&gt;
&lt;/p&gt;

&lt;%= form.submit %&gt;

<% end %> Now from one of our book’s pages. Click the edit link. You should see a familiar form: Notice how Rails filled in the inputs with the saved values? Nice huh. Go ahead and save some changes to this book. When you’re done you should see this: Now delete that book. You should get a confirmation dialog then be redirected back to /books. Add a link to create a new book on the index page. Open up /app/views/books/index.html.erb and add this to the bottom: <p> Admin actions: <%= link_to 'New Book', new_book_path %> </p> Now that we have CRUD functionality. We need to create our admin user. Fifty Five Maintaing user logins is a solved problem in Rails. You rarely have to write your own authentication system. We are going to use the authlogic gem. Authlogic provides simple mechanics to authenticate users and store sessions. This is prefect for our app. We need an admin to login so he can create/edit/delete books. First let’s start by installing the authlogic gem. # add config.gem 'authlogic' in environment.rb bookshelf $ sudo rake gems:install Create a new model to hold the admins. Since our users are only admins, we’ll name the model Admin. For now the model only needs a login attribute. Generate the model using script/generate model: bookshelf $ ./script/generate model Admin login:string exists app/models/ exists test/unit/ exists test/fixtures/ create app/models/admin.rb create test/unit/admin_test.rb create test/fixtures/admins.yml exists db/migrate create db/migrate/20091204202129_create_admins.rb Now add authlogic specific columns to our admin model. Open up the migration you just created and paste this into it: class CreateAdmins < ActiveRecord::Migration def self.up create_table :admins do |t| t.string :login t.string :crypted_password, :null => false t.string :password_salt, :null => false t.string :persistence_token, :null => false t.timestamps end end

def self.down
  drop_table :admins
end

end Now migrate your database. bookshelf $ rake db:migrate == CreateAdmins: migrating =================================================== -- create_table(:admins) -> 0.0025s == CreateAdmins: migrated (0.0026s) ========================================== Now the admin model is created. Next we need to create an authlogic session for that admin. Authlogic includes a generator for this: bookshelf $ ./script/generate session admin_session exists app/models/ create app/models/admin_session.rb Next we need to create some routes for logging in and out. Open up routes.rb and add this line: map.resource :admin_session Now we need a controller to handle the logging in and out. Generate this controller using the generator: bookshelf $ ./script/generate controller AdminSessions exists app/controllers/ exists app/helpers/ create app/views/admin_sessions exists test/functional/ exists test/unit/helpers/ create app/controllers/admin_sessions_controller.rb create test/functional/admin_sessions_controller_test.rb create app/helpers/admin_sessions_helper.rb create test/unit/helpers/admin_sessions_helper_test.rb Now open up /app/controllers/admin_sessions_controller.rb and paste this into it: class AdminSessionsController < ApplicationController def new @admin_session = AdminSession.new end

def create
  @admin_session = AdminSession.new(params[:admin_session])
  if @admin_session.save
    flash[:notice] = &quot;Login successful!&quot;
    redirect_to books_path
  else
    render :action =&gt; :new
  end
end

def destroy
  current_admin_session.destroy
  flash[:notice] = &quot;Logout successful!&quot;
  redirect_to books_path
end

end Wow! It seems like we just did a lot, but we haven’t. We’ve just created 2 new models. One model to hold our admins, and the other to hold admin session information. Finally we created a controller to handle the logging in and out. Now we need a view to show a login form. Create a new file at /app/views/admin_sessions/new.html.erb and paste this into it: <% title 'Login' %>

<% form_for @admin_session, :url => admin_session_path do |f| %> <%= f.error_messages %> <p> <%= f.label :login %><br /> <%= f.text_field :login %> </p>

&lt;p&gt;
  &lt;%= f.label :password %&gt;&lt;br /&gt;
  &lt;%= f.password_field :password %&gt;
&lt;/p&gt;

&lt;%= f.submit &quot;Login&quot; %&gt;

<% end %> We’re almost done. We still need to tell our Admin model that it uses authlogic and add some logic to our application controller to maintain session information. All controller inherit from application_controller, so it’s a good way to share methods between controllers. Open up /app/controllers/application_controller.rb and paste this: class ApplicationController < ActionController::Base helper :all # include all helpers, all the time protect_from_forgery # See ActionController::RequestForgeryProtection for details

# Scrub sensitive parameters from your log
# filter_parameter_logging :password

filter_parameter_logging :password, :password_confirmation
helper_method :current_admin_session, :current_admin

private
def current_admin_session
  return @current_admin_session if defined?(@current_admin_session)
  @current_admin_session = AdminSession.find
end

def current_admin
  return @current_admin if defined?(@current_admin)
  @current_admin = current_admin_session &amp;&amp; current_admin_session.user
end

end Now in /app/models/admin.rb add this line: # /app/models/admin.rb acts_as_authentic Ok, we are finally ready to do some logging in and out. All of the stuff we did was almost purely from the authlogic documentation examples. This is a standard setup for many applications. If you want to find out more about how authlogic works you can here. Here’s a run down of what we did.Install the authlogic gemCreate an Admin model to hold the basic information like login/passwordAdd authlogic specific columns to the Admin tableGenerated an authlogic admin sessionCreated routes for logging in and outGenerated an AdminSession controller to do all the workCreated a view that shows a login formAdded methods to ApplicationController for persisting sessionsTold the Admin model that it uses authlogic it’s time to create the admin. Our application is simple and it only has one admin. Since we only have one admin, we can easily create that one user from the console. Since we’ll need to recreate that user later on when we deploy, it doesn’t make sense to do the same thing twice. Rails now has a functionality for seeding the database. This perfect for creating the initial records our app needs to work. There is a file /db/seeds.rb where you can write ruby code to create your initial models. Then you can run this file through rake db:seed. In order to create our admin model we’ll need a login, password, and password confirmation. Open up /db/seeds.rb and paste this. Fill in the login with the name you want. Admin.create! :login => 'Adam', :password => 'nettuts', :password_confirmation => 'nettuts' We use the create! method because it will throw an exception if the record can’t be saved. Go ahead and run the rake task to seed the database: bookshelf $ rake db:seed Now we should be able to login. Restart the server to get the new routes. Head to /admin_session/new. You should see: Go ahead and fill it in and now you should be logged in! Now that admins can login, we can give them access to the new/edit/delete functionality. Rails has these awesome things called filters. Filters are things you can do at points in the request lifecycle. The most popular filter is a before_filter. This filter gets executed before an action in the controller. We can create a before filter in the books controller that checks to see if we have a logged in admin. The filter will redirect users who aren’t logged in therefore preventing unauthorized access. Open up books_controller.rb and add these lines: # first line inside the class: before_filter :login_required, :except => [:index, :show]

# after all the actions private def login_required unless current_admin flash[:error] = 'Only logged in admins an access this page.' redirect_to books_path end end Now we need to update our views to show the admin links only if there is an admin logged in. That’s easy enough. All we need to do is wrap it in an if. # show.html.erb <% if current_admin %> <p> Admin Actions: <%= link_to 'Edit', edit_book_path(@book) %> | <%= link_to 'Delete', book_path(@book), :method => :delete, :confirm => "Are you sure?" %> </p> <% end %>

# index.html.erb <% if current_admin %> <p> Admin actions: <%= link_to 'New Book', new_book_path %> </p> <% end %> We still need to add a login/logout link. That should go on every page. An easy way to put something on every page is add it to the layout. # /app/views/layouts/application.erb <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html> <head> <title><%= h(yield(:title) || "Untitled") %></title> <%= stylesheet_link_tag 'application' %> <%= yield(:head) %> </head> <body> <div id="container"> <%- flash.each do |name, msg| -%> <%= content_tag :div, msg, :id => "flash_#{name}" %> <%- end -%>

    &lt;%- if show_title? -%&gt;
      &lt;h1&gt;&lt;%=h yield(:title) %&gt;&lt;/h1&gt;
    &lt;%- end -%&gt;

    &lt;%= yield %&gt;

    &lt;% if current_admin %&gt;
      &lt;p&gt;&lt;%= link_to &#39;Logout&#39;, admin_session_path(current_admin_session), :method =&gt; :delete %&gt;&lt;/p&gt;
    &lt;% else %&gt;
      &lt;p&gt;&lt;%= link_to &#39;Login&#39;, new_admin_session_path %&gt;&lt;/p&gt;
    &lt;% end %&gt;
  &lt;/div&gt;
&lt;/body&gt;

</html> Now you should have login/logout links on pages depending if your logged in and logged out. Go ahead and click the through the app. Try access the new book page after you’ve logged out. You should see an error message. Go head and click through the app. You should be able to login and out, and edit/create/delete books. Time for the final step. let’s add some formatting to your thoughts and user comments. Rails has a helper method that will change new lines to line breaks and that sorta thing. let’s add that show.html.erb: # <p><%= @book.thoughts %></p> becomes <%= simple_format @book.thoughts %>

# do the same thing for comments # <p><strong><%=h(comment.author) %></strong>: <%=h comment.text %> becomes <p><strong><%=h(comment.author) %></strong>:</p> <%= simple_format comment.text %> It doesn’t make sense to put the thoughts in the index, so let’s replace that with a preview instead of the entire text. # index.html.erb # <p><%= book.thoughts %></p> becomes <%= simple_format(truncate(book.thoughts, 100)) %> Now our final index page should look like this: Finally we need to set up a route for our root page. Open up routes.rb and add this line: map.root :controller => 'books', :action => 'index' Now when you go to / you’ll see the book listing. Sixty Now we are going to deploy this app in a few steps. You don’t need your own server or anything like that. All you need is an account on Heroku. Heroku is a cloud Rails hosting service. If you have a small app, you can use their service for free. Once you’ve signed up for an account, install the heroku gem: $ sudo gem install heroku Heroku works with git. Git is a distributed source control management system. In order to deploy to heroku all you need to do is create your app then push your code to it’s server. If you haven’t already install git. Instructions can be found here. Once you have heroku and git installed you are ready to deploy. First thing we need to do is create a new git repo out of your project: bookshelf $ git init Initialized empty Git repository in /Users/adam/Code/bookshelf/.git/ It’s time to do some preparation for heroku deployment. In order to get your applications gems installed, you need to create a .gems file in the root project directory. Each line has the name of the gem on it. When you push your code to heroku it will read the .gems file and install the gems for you. So create a .gems file and paste this into it: forgery will_paginate authlogic There is a problem with authlogic on heroku, so we need to create an initializer to require the gem for us. Create a new file in /config/initializers/authlogic.rb and put this line in there: require 'authlogic' Now we should be ready to deploy. First thing you’re going to do is run heroku create. This will create a new heroku app for you. If you’re a first time user, it will guide you through the setup process. bookshelf $ heroku create Git remote heroku added No we are ready to deploy. Here are the stepsAdd all files in the project to a commitCommit the filesPush are code to herokuMigrate the database on herokuSeed the database on herokuRestart the heroku serverOpen your running application bookshelf $ git add -A bookshelf $ git commit -m 'Initial commit' bookshelf $ git push heroku master bookshelf $ heroku rake db:migrate bookshelf $ heroku rake db:seed bookshelf $ heroku restart bookshelf $ heroku open Here is the finally app running on the world wide web: Hit the Brakes We’ve covered a lot of ground in this article, so where do we go from here? Three are few things we didn’t do in this app. We didn’t add any validations to the models. We didn’t use partials. We didn’t do any administration for the comments. These are things you should look into next. Here are some links to help you with the next steps. Completed Source CodeConfused about the form parts? Read thisConfused about routes? Read thisConfused about heroku? Read thisConfused about associations? Read thisConfused about authlogic? Read this Links to gems used in this project. Forgery Authlogic Will PaginateFollow us on Twitter, or subscribe to the Nettuts+ RSS Feed for the best web development tutorials on the web. ReadyReady to take your skills to the next level, and start profiting from your scripts and components? Check out our sister marketplace, CodeCanyon.

]]>
Mon, 21 Dec 2009 02:30:26 -0800 http://www.federicobond.com.ar/items/view/413/zero-to-sixty-creating-and-deploying-a-rails-app-in-under-an-hour
Top 20+ MySQL Best Practices http://www.federicobond.com.ar/items/view/185/top-20-mysql-best-practices

Database operations often tend to be the main bottleneck for most web applications today. It’s not only the DBA’s (database administrators) that have to worry about these performance issues. We as programmers need to do our part by structuring tables properly, writing optimized queries and better code. Here are some MySQL optimization techniques for programmers.

  1. Optimize Your Queries For the Query Cache Most MySQL servers have query caching enabled. It’s one of the most effective methods of improving performance, that is quietly handled by the database engine. When the same query is executed multiple times, the result is fetched from the cache, which is quite fast. The main problem is, it is so easy and hidden from the programmer, most of us tend to ignore it. Some things we do can actually prevent the query cache from performing its task.

// query cache does NOT work $r = mysql_query("SELECT username FROM user WHERE signup_date >= CURDATE()");

// query cache works! $today = date("Y-m-d"); $r = mysql_query("SELECT username FROM user WHERE signup_date >= '$today'");

The reason query cache does not work in the first line is the usage of the CURDATE() function. This applies to all non-deterministic functions like NOW() and RAND() etc… Since the return result of the function can change, MySQL decides to disable query caching for that query. All we needed to do is to add an extra line of PHP before the query to prevent this from happening. 2. EXPLAIN Your SELECT Queries Using the EXPLAIN keyword can give you insight on what MySQL is doing to execute your query. This can help you spot the bottlenecks and other problems with your query or table structures. The results of an EXPLAIN query will show you which indexes are being utilized, how the table is being scanned and sorted etc… Take a SELECT query (preferably a complex one, with joins), and add the keyword EXPLAIN in front of it. You can just use phpmyadmin for this. It will show you the results in a nice table. For example, let’s say I forgot to add an index to a column, which I perform joins on:

After adding the index to the group_id field:

Now instead of scanning 7883 rows, it will only scan 9 and 16 rows from the 2 tables. A good rule of thumb is to multiply all numbers under the “rows” column, and your query performance will be somewhat proportional to the resulting number. 3. LIMIT 1 When Getting a Unique Row Sometimes when you are querying your tables, you already know you are looking for just one row. You might be fetching a unique record, or you might just be just checking the existence of any number of records that satisfy your WHERE clause. In such cases, adding LIMIT 1 to your query can increase performance. This way the database engine will stop scanning for records after it finds just 1, instead of going thru the whole table or index.

// do I have any users from Alabama?

// what NOT to do: $r = mysql_query("SELECT * FROM user WHERE state = 'Alabama'"); if (mysql_num_rows($r) > 0) { // ... }

// much better: $r = mysql_query("SELECT 1 FROM user WHERE state = 'Alabama' LIMIT 1"); if (mysql_num_rows($r) > 0) { // ... }

  1. Index the Search Fields Indexes are not just for the primary keys or the unique keys. If there are any columns in your table that you will search by, you should almost always index them.

As you can see, this rule also applies on a partial string search like “last_name LIKE ‘a%’”. When searching from the beginning of the string, MySQL is able to utilize the index on that column. You should also understand which kinds of searches can not use the regular indexes. For instance, when searching for a word (e.g. “WHERE post_content LIKE ‘%apple%’”), you will not see a benefit from a normal index. You will be better off using mysql fulltext search or building your own indexing solution. 5. Index and Use Same Column Types for Joins If your application contains many JOIN queries, you need to make sure that the columns you join by are indexed on both tables. This affects how MySQL internally optimizes the join operation. Also, the columns that are joined, need to be the same type. For instance, if you join a DECIMAL column, to an INT column from another table, MySQL will be unable to use at least one of the indexes. Even the character encodings need to be the same type for string type columns.

// looking for companies in my state $r = mysql_query("SELECT company_name FROM users LEFT JOIN companies ON (users.state = companies.state) WHERE users.id = $user_id");

// both state columns should be indexed // and they both should be the same type and character encoding // or MySQL might do full table scans

  1. Do Not ORDER BY RAND() This is one of those tricks that sound cool at first, and many rookie programmers fall for this trap. You may not realize what kind of terrible bottleneck you can create once you start using this in your queries. If you really need random rows out of your results, there are much better ways of doing it. Granted it takes additional code, but you will prevent a bottleneck that gets exponentially worse as your data grows. The problem is, MySQL will have to perform RAND() operation (which takes processing power) for every single row in the table before sorting it and giving you just 1 row.

// what NOT to do: $r = mysql_query("SELECT username FROM user ORDER BY RAND() LIMIT 1");

// much better:

$r = mysql_query("SELECT count(*) FROM user"); $d = mysql_fetch_row($r); $rand = mt_rand(0,$d[0] - 1);

$r = mysql_query("SELECT username FROM user LIMIT $rand, 1");

So you pick a random number less than the number of results and use that as the offset in your LIMIT clause. 7. Avoid SELECT * The more data is read from the tables, the slower the query will become. It increases the time it takes for the disk operations. Also when the database server is separate from the web server, you will have longer network delays due to the data having to be transferred between the servers. It is a good habit to always specify which columns you need when you are doing your SELECT’s.

// not preferred $r = mysql_query("SELECT * FROM user WHERE user_id = 1"); $d = mysql_fetch_assoc($r); echo "Welcome {$d['username']}";

// better: $r = mysql_query("SELECT username FROM user WHERE user_id = 1"); $d = mysql_fetch_assoc($r); echo "Welcome {$d['username']}";

// the differences are more significant with bigger result sets

  1. Almost Always Have an id Field In every table have an id column that is the PRIMARY KEY, AUTO_INCREMENT and one of the flavors of INT. Also preferably UNSIGNED, since the value can not be negative. Even if you have a users table that has a unique username field, do not make that your primary key. VARCHAR fields as primary keys are slower. And you will have a better structure in your code by referring to all users with their id’s internally. There are also behind the scenes operations done by the MySQL engine itself, that uses the primary key field internally. Which become even more important, the more complicated the database setup is. (clusters, partitioning etc…). One possible exception to the rule are the “association tables”, used for the many-to-many type of associations between 2 tables. For example a “posts_tags” table that contains 2 columns: post_id, tag_id, that is used for the relations between two tables named “post” and “tags”. These tables can have a PRIMARY key that contains both id fields.
  2. Use ENUM over VARCHAR ENUM type columns are very fast and compact. Internally they are stored like TINYINT, yet they can contain and display string values. This makes them a perfect candidate for certain fields. If you have a field, which will contain only a few different kinds of values, use ENUM instead of VARCHAR. For example, it could be a column named “status”, and only contain values such as “active”, “inactive”, “pending”, “expired” etc… There is even a way to get a “suggestion” from MySQL itself on how to restructure your table. When you do have a VARCHAR field, it can actually suggest you to change that column type to ENUM instead. This done using the PROCEDURE ANALYSE() call. Which brings us to:
  3. Get Suggestions with PROCEDURE ANALYSE() PROCEDURE ANALYSE() will let MySQL analyze the columns structures and the actual data in your table to come up with certain suggestions for you. It is only useful if there is actual data in your tables because that plays a big role in the decision making. For example, if you created an INT field for your primary key, however do not have too many rows, it might suggest you to use a MEDIUMINT instead. Or if you are using a VARCHAR field, you might get a suggestion to convert it to ENUM, if there are only few unique values. You can also run this by clicking the “Propose table structure” link in phpmyadmin, in one of your table views.

Keep in mind these are only suggestions. And if your table is going to grow bigger, they may not even be the right suggestions to follow. The decision is ultimately yours. 11. Use NOT NULL If You Can Unless you have a very specific reason to use a NULL value, you should always set your columns as NOT NULL. First of all, ask yourself if there is any difference between having an empty string value vs. a NULL value (for INT fields: 0 vs. NULL). If there is no reason to have both, you do not need a NULL field. (Did you know that Oracle considers NULL and empty string as being the same?) NULL columns require additional space and they can add complexity to your comparison statements. Just avoid them when you can. However, I understand some people might have very specific reasons to have NULL values, which is not always a bad thing. From MySQL docs: “NULL columns require additional space in the row to record whether their values are NULL. For MyISAM tables, each NULL column takes one bit extra, rounded up to the nearest byte.” 12. Prepared Statements There are multiple benefits to using prepared statements, both for performance and security reasons. Prepared Statements will filter the variables you bind to them by default, which is great for protecting your application against SQL injection attacks. You can of course filter your variables manually too, but those methods are more prone to human error and forgetfulness by the programmer. This is less of an issue when using some kind of framework or ORM. Since our focus is on performance, I should also mention the benefits in that area. These benefits are more significant when the same query is being used multiple times in your application. You can assign different values to the same prepared statement, yet MySQL will only have to parse it once. Also latest versions of MySQL transmits prepared statements in a native binary form, which are more efficient and can also help reduce network delays. There was a time when many programmers used to avoid prepared statements on purpose, for a single important reason. They were not being cached by the MySQL query cache. But since sometime around version 5.1, query caching is supported too. To use prepared statements in PHP you check out the mysqli extension or use a database abstraction layer like PDO.

// create a prepared statement if ($stmt = $mysqli->prepare("SELECT username FROM user WHERE state=?")) {

// bind parameters $stmt->bind_param("s", $state);

// execute $stmt->execute();

// bind result variables $stmt->bind_result($username);

// fetch value $stmt->fetch();

printf(&quot;%s is from %s\n&quot;, $username, $state);

$stmt-&gt;close();

}

  1. Unbuffered Queries Normally when you perform a query from a script, it will wait for the execution of that query to finish before it can continue. You can change that by using unbuffered queries. There is a great explanation in the PHP docs for the mysql_unbuffered_query() function: “mysql_unbuffered_query() sends the SQL query query to MySQL without automatically fetching and buffering the result rows as mysql_query() does. This saves a considerable amount of memory with SQL queries that produce large result sets, and you can start working on the result set immediately after the first row has been retrieved as you don’t have to wait until the complete SQL query has been performed.” However, it comes with certain limitations. You have to either read all the rows or call mysql_free_result() before you can perform another query. Also you are not allowed to use mysql_num_rows() or mysql_data_seek() on the result set.
  2. Store IP Addresses as UNSIGNED INT Many programmers will create a VARCHAR(15) field without realizing they can actually store IP addresses as integer values. With an INT you go down to only 4 bytes of space, and have a fixed size field instead. You have to make sure your column is an UNSIGNED INT, because IP Addresses use the whole range of a 32 bit unsigned integer. In your queries you can use the INET_ATON() to convert and IP to an integer, and INET_NTOA() for vice versa. There are also similar functions in PHP called ip2long() and long2ip().

$r = "UPDATE users SET ip = INET_ATON('{$_SERVER['REMOTE_ADDR']}') WHERE user_id = $user_id";

  1. Fixed-length (Static) Tables are Faster When every single column in a table is “fixed-length”, the table is also considered “static” or “fixed-length”. Examples of column types that are NOT fixed-length are: VARCHAR, TEXT, BLOB. If you include even just 1 of these types of columns, the table ceases to be fixed-length and has to be handled differently by the MySQL engine. Fixed-length tables can improve performance because it is faster for MySQL engine to seek through the records. When it wants to read a specific row in a table, it can quickly calculate the position of it. If the row size is not fixed, every time it needs to do a seek, it has to consult the primary key index. They are also easier to cache and easier to reconstruct after a crash. But they also can take more space. For instance, if you convert a VARCHAR(20) field to a CHAR(20) field, it will always take 20 bytes of space regardless of what is it in. By using “Vertical Partitioning” techniques, you can separate the variable-length columns to a separate table. Which brings us to:
  2. Vertical Partitioning Vertical Partitioning is the act of splitting your table structure in a vertical manner for optimization reasons. Example 1: You might have a users table that contains home addresses, that do not get read often. You can choose to split your table and store the address info on a separate table. This way your main users table will shrink in size. As you know, smaller tables perform faster. Example 2: You have a “last_login” field in your table. It updates every time a user logs in to the website. But every update on a table causes the query cache for that table to be flushed. You can put that field into another table to keep updates to your users table to a minimum. But you also need to make sure you don’t constantly need to join these 2 tables after the partitioning or you might actually suffer performance decline.
  3. Split the Big DELETE or INSERT Queries If you need to perform a big DELETE or INSERT query on a live website, you need to be careful not to disturb the web traffic. When a big query like that is performed, it can lock your tables and bring your web application to a halt. Apache runs many parallel processes/threads. Therefore it works most efficiently when scripts finish executing as soon as possible, so the servers do not experience too many open connections and processes at once that consume resources, especially the memory. If you end up locking your tables for any extended period of time (like 30 seconds or more), on a high traffic web site, you will cause a process and query pileup, which might take a long time to clear or even crash your web server. If you have some kind of maintenance script that needs to delete large numbers of rows, just use the LIMIT clause to do it in smaller batches to avoid this congestion.

while (1) { mysql_query("DELETE FROM logs WHERE log_date <= '2009-10-01' LIMIT 10000"); if (mysql_affected_rows() == 0) { // done deleting break; } // you can even pause a bit usleep(50000); }

  1. Smaller Columns Are Faster With database engines, disk is perhaps the most significant bottleneck. Keeping things smaller and more compact is usually helpful in terms of performance, to reduce the amount of disk transfer. MySQL docs have a list of Storage Requirements for all data types. If a table is expected to have very few rows, there is no reason to make the primary key an INT, instead of MEDIUMINT, SMALLINT or even in some cases TINYINT. If you do not need the time component, use DATE instead of DATETIME. Just make sure you leave reasonable room to grow or you might end up like Slashdot.
  2. Choose the Right Storage Engine The two main storage engines in MySQL are MyISAM and InnoDB. Each have their own pros and cons. MyISAM is good for read-heavy applications, but it doesn't scale very well when there are a lot of writes. Even if you are updating one field of one row, the whole table gets locked, and no other process can even read from it until that query is finished. MyISAM is very fast at calculating SELECT COUNT(*) types of queries. InnoDB tends to be a more complicated storage engine and can be slower than MyISAM for most small applications. But it supports row-based locking, which scales better. It also supports some more advanced features such as transactions.

MyISAM Storage Engine InnoDB Storage Engine

  1. Use an Object Relational Mapper By using an ORM (Object Relational Mapper), you can gain certain performance benefits. Everything an ORM can do, can be coded manually too. But this can mean too much extra work and require a high level of expertise. ORM's are great for "Lazy Loading". It means that they can fetch values only as they are needed. But you need to be careful with them or you can end up creating to many mini-queries that can reduce performance. ORM's can also batch your queries into transactions, which operate much faster than sending individual queries to the database. Currently my favorite ORM for PHP is Doctrine. I wrote an article on how to install Doctrine with CodeIgniter.
  2. Be Careful with Persistent Connections Persistent Connections are meant to reduce the overhead of recreating connections to MySQL. When a persistent connection is created, it will stay open even after the script finishes running. Since Apache reuses it's child processes, next time the process runs for a new script, it will reuse the same MySQL connection.

mysql_pconnect() in PHP

It sounds great in theory. But from my personal experience (and many others), this features turns out to be not worth the trouble. You can have serious problems with connection limits, memory issues and so on. Apache runs extremely parallel, and creates many child processes. This is the main reason that persistent connections do not work very well in this environment. Before you consider using the mysql_pconnect() function, consult your system admin.

Follow us on Twitter, or subscribe to the Nettuts+ RSS Feed for the best web development tutorials on the web.

]]>
Wed, 25 Nov 2009 02:30:00 -0800 http://www.federicobond.com.ar/items/view/185/top-20-mysql-best-practices