Monday, February 17, 2014

Learning Notes about Agile Web Development with Rails (4)

Rails application --- Depot

Ruby on Rails shines when it comes to dealing with change—it’s an ideal agile
programming environment.

Along the way we will be building and maintaining a corpus of tests. These
tests will ensure that the application is always doing what we intend to do.
Rails not only enables the creation of such tests, it actually provides you with
an initial set of tests each time you define a new controller.

1. Use Cases
A use case is simply a statement about how some entity uses a system. Con-
sultants invent these kinds of phrases to label things we’ve all known all
along—it’s a perversion of business life that fancy words always cost more
than plain ones, even though the plain ones are more valuable.

2. Page Flow
Some folks like to mock up web application page flows using Photoshop, Word,
or (shudder) HTML. We like using a pencil and paper. It’s quicker, and the
customer gets to play too, grabbing the pencil and scribbling alterations right
on the paper.

3. Data
Finally, we need to think about the data we’re going to be working with.

Notice that we’re not using words such as schema or classes here. We’re also
not talking about databases, tables, keys, and the like. We’re simply talking
about data. At this stage in the development, we don’t know whether we’ll even
be using a database.

4. 
Task A: Creating the Application
Iteration A1: Creating the Products Maintenance Application
Creating a Rails Application
Creating the Database
Generating the Scaffold
Applying the Migration (Rake looks for all the migrations not yet applied to the database
and applies them.)

Iteration A2: Making Prettier Listings
Our customer has one more request (customers always seem to have one more
request). 

this code uses %{...}. This is an alternative syntax for double-quoted
string literals, convenient for use with long strings. 

Rails keeps a separate file that is used to create a standard page environment 
for the entire application. This file, called application.html.erb, is a Rails layout 
and lives in the layouts directory

• We created a development database.
• We used migration to create and modify the schema in our development
database.
• We created the products table and used the scaffold generator to write an
application to maintain it.
• Updated an application wide layout as well as controller specific view in
order to show a list of products.


Task B: Validation and Unit Testing
Iteration B1: Validating!
The model layer is the gatekeeper between
the world of code and the database. Nothing to do with our application comes
out of the database or gets stored into the database that doesn’t first go
through the model. This makes models an ideal place to put validations; it
doesn’t matter whether the data comes from a form or from some program-
matic manipulation in our application. If a model checks it before writing to
the database, then the database will be protected from bad data.

Iteration B2: Unit Testing of Models
An assertion is simply a method call that tells the framework what we expect to be true. 

Test Fixtures
In the world of testing, a fixture is an environment in which you can run a
test. 

In the world of Rails, a test fixture is simply a specification of the initial con-
tents of a model (or models) under test. If, for example, we want to ensure that
our products table starts off with known data at the start of every unit test, we
can specify those contents in a fixture, and Rails will take care of the rest.

What We Just Did
• We ensured that required fields were present.
• We ensured that price fields were numeric, and at least one cent.
• We ensured that titles were unique.
• We ensured that images matched a given format.
• We updated the unit tests that Rails provided, both to conform to the
constraints we have imposed on the model and to verify the new code
that we added.

Task C: Catalog Display
Iteration C1: Creating the Catalog Listing
Iteration C2: Adding a Page Layout
Iteration C3: Using a Helper to Format the Price
Iteration C4: Functional Testing of Controllers
Both validation and functional tests will only test the behavior of controllers, 
they will not retroactively affect any data that already exists in the database or 
in fixtures.

What We Just Did
We’ve put together the basis of the store’s catalog display. The steps were as
follows:
1. Create a new controller to handle customer-centric interactions.
2. Implement the default index action.
3. Add a default_scope to the Product model to specify the order to list the
items on the website.
4. Implement a view (an .html.erb file) and a layout to contain it (another
.html.erb file).
5. Use a helper to format prices the way we want.
6. Make use of a CSS stylesheet.
7. Write functional tests for our controller.

Task D: Cart Creation
Iteration D1: Finding a Cart
Iteration D2: Connecting Products to Carts
There’s an easy way to remember where to put
belongs_to declarations: if a table has foreign keys, the corresponding model
should have a belongs_to for each.
Iteration D3: Adding a Button

What We Just Did
It has been a busy, productive day so far. We’ve added a shopping cart to our
store, and along the way we’ve dipped our toes into some neat Rails features:
• We created a Cart object in one request and were able to successfully
locate the same Cart in subsequent requests using a session object,
• we added a private method in the base class for all of our controllers,
making it accessible to all of our controllers,
• we created relationships between Carts and Line Items, relationships
between Line Items and Products, and were able to navigate using these
relationships, and
• we added a button which caused a product to be POSTed to a cart, caus-
ing a new LineItem to be created.

Task E: A Smarter Cart
Iteration E1: Creating a Smarter Cart
Iteration E2: Handling Errors
Rails has a convenient way of dealing with errors and error reporting. It defines
a structure called a flash. A flash is a bucket (actually closer to a Hash) in which
you can store stuff as you process a request. The contents of the flash are
available to the next request in this session before being deleted automatically.
Typically the flash is used to collect error messages. 

Iteration E3: Finishing the Cart


What We Just Did
Our shopping cart is now something the client is happy with. Along the way,
we covered:
• Adding a column to an existing table, with a default value
• Migrating existing data into the new table format
• Providing a flash notice of an error that was detected
• Using the logger to log events
• Deleted a record
• Adjusted the way a table is rendered, using CSS.

Task F: Add a Dash of Ajax
Whenever you work with Ajax, it’s good to start with the non-Ajax version of
the application and then gradually introduce Ajax features. 

Iteration F1: Moving the Cart
A partial is simply a chunk of a view in its own separate file. You can
invoke (render) a partial from another template or from a controller, and the
partial will render itself and return the results of that rendering. And, just as
with methods, you can pass parameters to a partial, so the same partial can
render different results.

Rails automatically prepends an underscore to the partial name when looking 
for the file. 

Iteration F2: Creating an Ajax-Based Cart
Ajax lets us write code that runs in the browser that interacts with our server-
based application.

Iteration F3: Highlighting Changes

Iteration F4: Hiding an Empty Cart
Whenever we want to abstract some processing out of a view (any kind of view),
we should write a helper method.

What We Just Did
In this iteration we added Ajax support to our cart:
• We moved the shopping cart into the sidebar. We then arranged for the
create action to redisplay the catalog page.
• We used :remote => true to invoke the LineItem.new action using Ajax.
• We then used an RJS template to update the page with just the cart’s
HTML.
• To help the user see changes to the cart, we added a highlight effect,
again using the RJS template.
• We wrote a helper method that hides the cart when it is empty and used
the RJS template to reveal it when an item is added.
• We wrote a test which verifies not only the creation of a line item but also
the content of the response that is returned from such a request.

First, if you plan to do a lot of Ajax development, you’ll probably need to get familiar with your browser’s Java Script debugging facilities and with its DOM inspectors, such as Firefox’s Fire-
bug, Internet Explorer 8’s Developer Tools, Google Chrome’s Developer Tools,
Safari’s Web Inspector, or Opera’s Dragonfly. And, second, the NoScript plug-
in for Firefox makes checking JavaScript/no JavaScript a one-click breeze.
Others find it useful to run two different browsers when they are developing—
have JavaScript enabled in one, disabled in the other. Then, as new features
are added, poking at it with both browsers will make sure your application
works regardless of the state of JavaScript.

Task G: Check Out!
Iteration G1: Capturing an Order
Iteration G2: Atom Feeds
Iteration G3: Pagination

What We Just Did
In a fairly short amount of time, we did the following:
• We created a form to capture details for the order and linked it to a new
order model.
• We added validation and used helper methods to display errors to the
user.
• We installed and used a plugin to paginate the list of orders.
• We provided a feed so that the administrator can monitor orders as they
come in.

Task H: Sending Mail
Iteration H1: Sending Confirmation E-mails
Iteration H2: Integration Testing of Applications

Unit testing of models
Model classes contain business logic. For example, when we add a prod-
uct to a cart, the cart model class checks to see whether that product is
already in the cart’s list of items. If so, it increments the quantity of that
item; if not, it adds a new item for that product.

Functional testing of controllers
Controllers direct the show. They receive incoming web requests (typ-
ically user input), interact with models to gather application state, and
then respond by causing the appropriate view to display something to the
user. So when we’re testing controllers, we’re making sure that a given
request is answered with an appropriate response. We still need models,
but we already have them covered with unit tests.

The next level of testing is to exercise the flow through our application. In many
ways, this is like testing one of the stories that our customer gave us when we
first started to code the application. For example, we might have been told
that A user goes to the store index page. They select a product, adding it to their
cart. They then check out, filling in their details on the checkout form. When they
submit, an order is created in the database containing their information, along
with a single line item corresponding to the product they added to their cart.
Once the order has been received, an e-mail is sent confirming their purchase.

This is ideal material for an integration test. Integration tests simulate a con-
tinuous session between one or more virtual users and our application. You
can use them to send in requests, monitor responses, follow redirects, and so
on.

(When you create a model or controller, Rails creates the corresponding unit or
functional tests. Integration tests are not automatically created, however, but
you can use a generator to create one.)

What We Just Did
Without much code, and a few templates, we have managed to pull off the
following:
• We configured our development, test, and production environments for
our Rails application to enable the sending of outbound e-mails.
• We created and tailored a mailer which will send confirmation e-mails in
both plain text and HTML formats to people who order our products.
• We created both a functional test for the e-mails produced,

Task I: Logging In
Iteration I1: Adding Users
Iteration I2: Authenticating Users

Iteration I3: Limiting Access
Rails filters allow you to intercept calls to action methods, adding your own
processing before they are invoked, after they return, or both. In our case, we’ll
use a before filter to intercept all calls to the actions in our admin controller.
The interceptor can check session[:user_id]. If set and if it corresponds to a user
in the database, the application knows an administrator is logged in, and the
call can proceed. If it’s not set, the interceptor can issue a redirect, in this case
to our login page.

Iteration I4: Adding a Sidebar, More Administration

What We Just Did
By the end of this iteration, we’ve done the following:
• We created a virtual attribute representing the plain-text password and
coded it to create the hashed version whenever the plain-text version is
updated.
• We controlled access to the administration functions using before filters
to invoke an authorize method.
• We saw how to use rails console to interact directly with a model (and dig
us out of a hole after we deleted the last user).
• We saw how a transaction can help prevent deleting the last user.

Task J: Internationalization
Iteration J1: Selecting the locale
Iteration J2: Translating the Store Front
Iteration J3: Translating Checkout
Iteration J4: Add a Locale Switcher

What We Just Did
By the end of this iteration, we’ve done the following:
• We set the default locale for our application, and provided a means for
the user to select an alternate locale.
• We created translation files for text fields, currency amounts, errors, and
model names.
• We altered layouts and views to call out to the I18n module by way of the
t helper in order to translate textual portions of the interface.

Task K: Deployment and Production
Iteration K1: Deploying with Phusion Passenger and MySQL

A deployed Rails application works a bit differently. We can’t
just fire up a single Rails server process and let it do all the work. Well, we
could, but it’s far from ideal. The reason for this is that Rails is single-threaded.
It can work on only one request at a time.

The Web, however, is an extremely concurrent environment. Production web
servers, such as Apache, Lighttpd, and Zeus, can work on several requests—
even tens or hundreds of requests—at the same time. A single-process, single-
threaded Ruby-based web server can’t possibly keep up. Luckily, it doesn’t
have to keep up. Instead, the way that we deploy a Rails application into pro-
duction is to use a front-end server, such as Apache, to handle requests from
the client. Then, you use the HTTP proxying of Passenger to send requests
that should be handled by Rails to one of any number of back-end application
processes.

SQLite is not recommended for high-volume, high concurrency web sites with large
datasets. 

There are plenty of alternatives to SQLite, both free and commercial. We will
go with MySQL. It already is included in OSX, is available via your native
packaging tool in Linux, 

Iteration K2: Deploying Remotely with Capistrano
Iteration K3: Checking Up on a Deployed Application

What We Just Did
We covered a lot of ground in this chapter. We’ve taken our code that ran locally
on our development machine for a single user and placed it on a different
machine, running a different web server, accessing a different database, and
possibly even running a different operating system.
To accomplish this, we used a number of different products:
• We installed and configured Phusion Passenger and Apache httpd, a pro-
duction quality web server.
• We installed and configured MySQL, a production quality database server.
• We got our application’s dependencies under control using Bundler and
Git.
• We installed and configured Capistrano, which enables us to confidently
and repeatably deploy our application.

No comments:

Post a Comment