Agile Web Development with Rails

0
0
555
2 months ago
Preview
Full text

greets fly out to koobe community :D

  No part of this publication may be reproduced, stored in a retrieval system, or transmitted, in any form, or by any means, electronic, mechanical, photocopying, recording, or otherwise,without the prior consent of the publisher. 6 Part I—Getting Started 8 2 The Architecture of Rails Applications 9 2.1 Models, Views, and Controllers .

9.2 Iteration D2: Show Cart Contents on Checkout . . . . . . 104

10.1 Iteration E1: Basic Shipping . . . . . . . . . . . . . . . . . 109

  129 11.5 More Icing on the Cake . 197 14.4 Connecting to the Database .

16 Action Controller and Rails 278

  335 17.6 Linking to Other Pages and Resources . 341 17.9 Layouts and Components .

21 Securing Your Rails Application 427

  433 21.4 Creating Records Directly from Form Parameters . 438 21.9 Knowing That It Works .

22 Deployment and Scaling 440

  448 22.3 Iterating in the Wild . 463 Part IV—Appendices466 A Introduction to Ruby467 A.1 Ruby Is an Object-Oriented Language .

Chapter 1 Introduction Ruby on Rails is a framework that makes it easier to develop, deploy, and maintain web applications. Of course, all web frameworks make the same claim. What makes Rails different? We can answer that question a number of ways. One way is to look at architecture. Over time, most developers have moved

  You’ll find very little duplication in a Rails application; you say whatyou need to say in one place—a place often suggested by the conventions of the MVC architecture—and then move on.is crucial, too. It turns out the best way to create aframework is to find the central themes in a specific application and then bottle them up in a generic foundation of code.

1.1 Rails Is Agile

  The strong, almost obsessive, way that Rails honors the DRY principle meansthat changes to Rails applications impact a lot less code than the same changes would in other frameworks. Then, as you read the deeper reference material in the back, see how the underlying structure of Rails can enable you tomeet your customers’ needs faster and with less ceremony.

1.2 Finding Your Way Around

  class SayController < ApplicationController end Turn to the cross-reference starting on page 512 , look up the corre- sponding number, and you’ll find the name of the file containing thatpiece of code. If you install Rails using RubyGems (which we recommend), simply start theGem documentation server (using the command gem_server ) and you can access all the Rails APIs by pointing your browser at http://localhost:8808 .

1.3 Acknowledgments

  Nathan Colgate Clark responded to a plea on the Rails mailing list and produced the wonderful image we use for the David Says... Last, but by no means least, we’d like to thank the folks who contributed the specialized chapters to the book: Leon Breedt, Mike Clark, ThomasFuchs, and Andreas Schwarz.

Chapter 2 The Architecture of Rails Applications One of the interesting things about Rails is that it imposes some fairly

  serious constraints on how you structure your web applications. Surpris- ingly, these constraints make it easier to create applications—a lot easier.

2.1 Models, Views, and Controllers

  In this simple case, it takes the first part of the path, , add_to_cart as the name of the controller and the second part, , as the name of an action. (See how the model is beingused to keep track of all the business data; the controller tells it what to do, and the model knows how to do it.)Now that the cart includes the new product, we can show it to the user.

2.2 Active Record: Rails Model Support

  While this is good from a conceptual point of view, it makes it difficult to combine relational databases with object-orientedprogramming languages. One organizes your program around the database; the other organizes the database around your program.

2 SQL directly into their code, either as strings or by using a preprocessor

  that converted SQL in their source into lower-level calls to the database engine. The integration meant that it became natural to intertwine the database logic with the overall application logic.

EXEC SQL BEGIN DECLARE SECTION;

int id; float amount;EXEC SQL END DECLARE SECTION; EXEC SQL DECLARE c1 AS CURSOR FORselect id, amount from orders;while (1) { float tax;2 SQL, referred to by some as Structured Query Language, is the language used to query and update relational databases. EXEC SQL WHENEVER NOT FOUND DO break ; EXEC SQL FETCH c1 INTO :id, :amount;tax = calc_sales_tax(amount) EXEC SQL UPDATE orders set tax = :tax where id = :id; } EXEC SQL CLOSE c1;

EXEC SQL COMMIT WORK;

  We just have to get the current time in our loop, add a column to the SQL update statement, and pass the time to the execute ( ) call. To illustrate this, here’s a program that uses Active Record to wrap orders our table.require ' active_record ' class Order < ActiveRecord::Baseendorder = Order.find(1) order.discount = 0.5order.save This code uses the new Order class to fetch the order with an id of 1 and modify the discount.

2.3 Action Pack: The View and Controller

  The controller supplies data to the view, and the controllerreceives back events from the pages generated by the views. By embedding code in the view we risk adding logic that should be in the model or the controller.

Chapter 3 Installing Rails Before you can start writing a Rails application, you’ll need to download

  All you need to runRails is a Ruby interpreter (version 1.8.2 or later) and the Rails code. Finally, if you use a database other than MySQL, you may need to install the appro-priate Ruby libraries to interface with it.

3.1 Installing on Windows

  You can verify this by starting the terminal application (use the Finder to navigate to Applications Utilities and double-click on Terminal ) and entering ruby -v → at the prompt. OS X will typically unpack the archivefile for you, so all you have to do is navigate to the downloaded direc- tory and (in the Terminal application) type dave> tar xzf rubygems-0.8.10.tar.gz dave> cd rubygems-0.8.10rubygems-0.8.10> sudo ruby setup.rb Password: <enter your password> 3.

3.4 Rails and Databases

  Once you’ve done that,you’ll also need to fix a minor problem in the Apple version of Ruby (unless you already installed the fix from Lucas Carlson described in the sidebaron the preceding page). The next time you restart your application it will pick up this latest version of Rails.(We have more to say about updating your application in production in the chapter, starting on page 440 .) Deployment and Scaling 3.6 Rails and ISPs If you’re looking to put a Rails application online in a shared hosting envi- ronment, you’ll need to find a Ruby-savvy ISP.

Chapter 4 Instant Gratification Let’s write a trivial web application to verify we’ve got Rails snugly installed

  on our machines. Along the way, we’ll get a glimpse of the way Rails applications work.

4.1 Creating a New Application

  As we’ll see later (in Section 13.2 , Directory Structure, on page 173 ), this means that we need to create a specific directory structure, slotting rails the code we write into the appropriate places. In that directory, use the rails command to createan application called demo .

4.2 Hello, Rails!

  The web server that is hosting your application is fairly smart3 The concept of the “name of the controller” is actually more complex than you mightthink, and we’ll explain it in detail in Section 13.4 , Naming Conventions, on page 180 . The first part of the path following the application is the name of the controller, and the second part is the name of the action.

4.3 Linking Pages Together

  The first parameter in the call to link_to ( ) is the text to be displayed in the hyperlink, and thenext parameter tells Rails to generate the link to the goodbye action. If we point our browser at our hello page, it will now contain the link to the goodbye page, as shown in Figure 4.7 .

4.4 What We Just Did

  In this chapter we constructed a toy application. Doing so showed us This is a great foundation.

Chapter 5 The Depot Application We could mess around all day hacking together simple test applications

  More seriously, it turns out that our shopping cart will illustrate many of the features of Rails development. Over the next eight chapters, we’ll also touch on peripheral topics such as unit testing, security, and page layout.

5.1 Incremental Development

  It doesn’t matter whatthe reason—the earlier we discover we’ve made a mistake, the less expen- sive it will be to fix that mistake. If we decide we need to add a new column to a databasetable, or change the navigation between pages, we need to be able to get in there and do it without a bunch of coding or configuration hassle.

5.2 What Depot Does

  The seller uses Depot to maintain a list of products to sell, to determine the orders that are awaiting shipping, and to mark orders as shipped.(The seller also uses Depot to make scads of money and retire to a tropical island, but that’s the subject of another book.)For now, that’s all the detail we need. Based on the use cases and the flows, it seems likely that we’ll be working with the data shown in Figure 5.3 .

5.3 Let’s Code

  So, after sitting down with the customer and doing some preliminary anal- ysis, we’re ready to start using a computer for development! In this case, I’d point out to her that it’s hard to develop anything else until wehave some basic products defined in the system, so I’d suggest spending a couple of hours getting the initial version of the product maintenancefunctionality up and running.

Chapter 6 Task A: Product Maintenance Our first development task is to create the web interface that lets us main-

  tain our product information—create new products, edit existing products, delete unwanted ones, and so on. We’ll develop this application in smalliterations, where small means “measured in minutes.” Let’s get started....

6.1 Iteration A1: Get Something Running

  To fix Apple’s bad install, you’re going to need to reinstall Ruby’s MySQL library, which means going back to on page 21 , running the script to repair the Rubymysql installation, and then reinstalling the gem.4 Some readers also report getting the error Client does not support authentication protocolrequested by server; consider upgrading MySQL client . When we run the generator, we tell it that we want a scaffold for a particu- lar model (which it creates) and that we want to access it through a givencontroller (which it also creates).name mapping In Rails, a model is automatically mapped to a database table whose name ֒→ page 180 is the plural form of the model’s class.

6.2 Iteration A2: Add a Missing Column

  This means we’ll need to add a column to the database table, and we’ll need to make sure that the various maintenance pages are updated to addsupport for this new column. Some developers (and DBAs) would add the column by firing up a utility program and issuing the equivalent of the command alter table products add column date_available datetime; Instead, I tend to maintain the flat file containing the DDL I originally used to create the schema.

6.3 Iteration A3: Validate!

  First, we’ll use the delightfully named validates_numericality_ofFile 65 ( ) method to verify that the price is a valid number.validates_numericality_of :price Now, if we add a product with an invalid price, the appropriate message 6 will appear.6price MySQL gives Rails enough metadata to know that contains a number, so Rails converts it to a floating-point value. :with => %r{^http:.+\.(gif|jpg|png)$}i,7 :message => "must be a URL for a GIF, JPG, or PNG image"Later on, we’d probably want to change this form to let the user select from a list of available images, but we’d still want to keep the validation to prevent malicious folks fromsubmitting bad data directly.

6.4 Iteration A4: Prettier Listings

  As developers, we’re trained to respond to these kinds of request with a sharp intake of breath, a knowing shakeof the head, and a murmured “you want what?” At the same time, we also like to show off a bit. (If you’re reading this on paper, you’ll sprintf have to take our word for it about the pastels.) We also used Ruby’s ( ) method to convert the floating-point price to a nicely formatted string.

Chapter 7 Task B: Catalog Display All in all, it’s been a successful day so far. We gathered the initial require-

  ments from our customer, documented a basic flow, worked out a first pass at the data we’ll need, and put together the maintenance page for theDepot application’s products. We should also be able to draw on the work we did in the product main- tenance task—the catalog display is really just a glorified product listing.

7.1 Iteration B1: Create the Catalog Listing

  (The condition checks that the item’s availabil- now ity date is not in the future. It uses the MySQL ( ) function to get the current date and time.) We asked our customer if she had a preference def self .salable_itemsfind(:all, :conditions => "date_available <= now()", :order => "date_available desc")end The find ( ) method returns an array containing a Product object for each row returned from the database. To do this, edit the file app/views/store/index.rhtml .(Remember that the path name to the view is built from the name of the controller ( store ) and the name of the action ( index ).

7.2 Iteration B2: Add Page Decorations

  To give the Add to Cart link a Figure 7.2: Catalog with Layout Added link_to class, we had to use the optional third parameter of the ( ) method, which lets us specify HTML attributes for the generated tag. (Listings of the stylesheets start on page 508 .) Hit Refresh, and the browser window looks something likeFigure 7.2 .

Chapter 8 Task C: Cart Creation Now that we have the ability to display a catalog containing all our won-

  derful products, it would be nice to be able to sell them. This is going to involve a number of new concepts, including sessions and parent-child relationships between database tables, so let’sget started.

8.1 Sessions

  (Although that’s all we have to say about namingfor now, you might want to look at Section 14.1 , Tables and Classes, on page 191 , for more information on how class and table names are related.) depot> ruby script/generate model LineItem Finally, we have to tell Rails about the reference between the line items and the product tables. As with most things in Rails, if theassumptions it makes about column names don’t work for your particular schema, you can always override it.1 For example, the popular MySQL database does not implement foreign keys internally unless you specify a particular underlying implementation for the corresponding tables.2 Yes, Rails is smart enough to know that a model called Child should map to a table named children .

1 Logical

  The cart is tied to the buyer’s session, and as long as that session data is available across all our servers(when we finally deploy in a multiserver environment), that’s probably good enough. Inside the cart we’ll hold line items (which correspond to rows in the line_items table), but we won’t save those line items into the databaseuntil we create the order at checkout.

8.3 Iteration C1: Creating a Cart

  It uses the object to get the parameter from the request and then finds the corresponding product, andit uses the find_cart ( ) method we created earlier to find the cart in the ses- sion and add the product to it. If we use the Back button to return to the catalog display and add another product to the cart, you’ll see the Figure 8.2: Our Cart Now Has an Item in It count update when the display cart page is displayed.

8.4 Iteration C2: Handling Errors

  For example, when our ( ) action detects that it was passed an invalid product id, it can store that errormessage in the flash area and redirect to the index ( ) action to redisplay the catalog. In the handler we use the Rails logger to record the error, create a flash noticewith an explanation, and redirect back to the catalog display.

8.5 Iteration C3: Finishing the Cart

  ers that this method does something destructive.) @items = [] @total_price = 0.0end Now when we click the Empty cart link, we get taken back to the catalog page, and a nice little message saysHowever, before we break an arm trying to pat ourselves on the back, let’s look back at our code. First, in the store controller, we now have three places that put a message into the flash and redirect to the index page.

Chapter 9 Task D: Checkout! Let’s take stock. So far, we’ve put together a basic product administration

  So now we need to let the buyer actually purchase thecontents of that cart. .

9.1 Iteration D1: Capturing an Order

  We’d better order.rb define the option array in the model before we forget.2 If we anticipate that other non-Rails applications will update the orders table, we mightwant to move the list of payment types into a separate lookup table and make the orders column a foreign key referencing that new table. (This is actually a very simple use of the compo-nent functionality; we’ll see it in more detail in Section 17.9 , Layouts andComponents , on page 356 .) As a first pass, let’s edit the view code in checkout.rhtml to include a call to File 38 <%= error_messages_for("order") %> render the cart at the top of the page, before the form.

Chapter 10 Task E: Shipping We’re now at the point where buyers can use our application to place orders. Our customer would like to see what it’s like to fulfill these orders. Now, in a fully fledged store application, fulfillment would be a large, com-

  We might need to integrate with various backend shipping agen- cies, we might need to generate feeds for customs information, and we’dprobably need to link into some kind of accounting backend. But even though we’re going to keep it simple, we’llstill have the opportunity to experiment with partial layouts, collections, and a slightly different interaction style to the one we’ve been using so far.

10.1 Iteration E1: Basic Shipping

  (I like to set different color schemes in the administration side of a site so that it’s immediatelyobvious that you’re working there.) And now we have a dedicated CSS file for the administration side of the application, we’ll move the list-related scaffold.css admin.css styles we added to back on page 65 into it. (This process is explained in more detail on page 341 .) In the case of our example, if just the single checkbox for @params = { "to_be_shipped" => { "1" => "yes" } } Because of this special handling of forms, we can iterate over all the check- boxes in the response from the browser and look for those that the ship-ping person has checked.

Chapter 11 Task F: Administrivia We have a happy customer—in a very short time we’ve jointly put together

  She’d like us to add a basic user administration system that would force you to log in to get into the administration partsof the site. We’re happy to do that, as it gives us a chance to play with callback hooks and filters, and it lets us tidy up the application somewhat.

11.1 Iteration F1: Adding Users

  Before the user row is saved, we use the before_create ( ) hook to take a plain-text password and apply the SHA1 hash function to it, storing theresult in the hashed_password attribute. We’d previously defined this in the store controller on page redirect_to_index ( ) method in the login controller calls the add_user The attr_accessor :password attr_accessible :name, :passwordvalidates_uniqueness_of :name validates_presence_of :name, :password Add a couple of validations, and the work on the user model is done (for now).

11.3 Iteration F3: Limiting Access

  We don’t want to delete all the administrative users from our system (because if we User prevent this, we use a hook method in the model, arranging for the method dont_destroy_dave ( ) to be called before a user is destroyed. id = params[:id]if id && user = User.find(id) beginuser.destroy flash[:notice] = "User #{user.name} deleted" rescuerescue֒→ page 477flash[:notice] = "Can t delete that user" 'end endredirect_to(:action => :list_users)end Updating the Sidebar Adding the extra administration functions to the sidebar is straightfoward.admin.rhtml We edit the layout and follow the pattern we used when adding the functions in the admin controller.

11.4 Finishing Up

  (We haven’t shown them in the code extracts in this book because we wanted to save space.) Rails makes it easy to run Ruby’sRDoc RDoc utility on all the source files in an application to create good-looking ֒→ page 480 You can generate the documentation in HTML format using the rake com- mand. depot> rake appdoc This generates documentation into the directory doc/app .

11.5 More Icing on the Cake

  Erik suggests extending it to save the incoming request parameters in the session before redirecting to log the user in. def authorize unless session[:user_id]flash[:notice] = "Please log in"# save the URL the user requested so we can hop back to it # after loginsession[:jumpto] = request.parameters redirect_to(:controller => "login", :action => "login")end end3http://wiki.rubyonrails.com/rails/show/AvailableGenerators Then, once the login is successful, use these saved parameters in a redirect to take the browser to the page the user originally intended to visit.

Chapter 12 Task T: Testing In short order we’ve developed a respectable web-based shopping cart

  Along the way, we got rapid feedback by writing a little code, and then punching buttons in a web browser (with our customer by ourside) to see if the application behaves as we expected. You’ll find listings of the code from this chapter starting on page 500 .

12.1 Tests Baked Right In

  We haven’t written a lick of test code for the Depot application, but if you look in the top-level directory of that project you’ll notice a subdirectory test called . Rails has already created files to hold the unit tests for the models and the functional tests for the controllers we created earlier with generate the script.

12.2 Testing Models

  This may not seem like much, but it actually tells us a lot: the test database is properly config-ured, the products table is populated with data from the fixture, ActiveRecord was successful in fetching a given Product object from the test database, and we have a passing test that actually tests something. If the update test runs before the create test, we mightthink we’d have a problem, because test_update ( ) alters the price in the database and test_create ( ) method runs it still expects the product’s priceto be the original value in the products.yml fixture file.

Dokumen baru