Chapter 6. Making Connections: Bringing it all together

image with no caption

Some things are stronger together than apart.

So far you’ve had a taste of some of the key Rails ingredients. You’ve created entire web applications and taken what Rails generates and customized it for your needs. But out in the real world, life can be more complex. Read on... it’s time to build some multi-functional web pages. Not only that, it’s time to deal with difficult data relationships and take control of your data by writing your own custom validators.

Coconut Airways need a booking system

image with no caption

There’s no better way of traveling between islands than by seaplane, and Coconut Airways has an entire fleet. They offer scenic tours, excursions, and a handy shuttle service between all the local islands. Their service is proving popular with tourists and locals alike.

Demand for their flights is sky-high, and they need an online reservation system to help them. The system needs to manage flight and seat bookings. Here’s the data they need to store:

image with no caption
image with no caption

We need to see flights and seat bookings together

If we simply create scaffolding and don’t customize the app, it will be hard to use. In order to book a seat on a flight, the user will have to look up the id of the flight from its URL:

image with no caption

We need to display a flight together with its seat bookings.

Let’s look at what the seat scaffolding gives us

We need the flight page to look something like this:

image with no caption

Let’s see how this compares with the seat pages generated by the scaffolding:

image with no caption

Can any of these help us generate the flight page?

We need the booking form and seat list on the flight page

Two of the generated pages look pretty similar to what we need on the flight page, the seat list and the booking form. The middle section of the flight page looks like the seat list, and the booking form looks like the end section:

image with no caption

So we need the flight “show” page to include view code like the seat “index” list and the new seat booking form.

So should we just copy the code from each form into the flight page?

image with no caption

Mark: Woah - wait a minute. How much code is that?

Laura: I dunno. We need the code in the page, though. It’s in the design.

Mark: I know we need the seat list and the booking form to appear in the page. But does that mean we have to have the code in there?

Laura: Why - what’s the problem with that code?

Mark: The seat list and the booking form are doing significantly different things. Can’t we break them apart somehow?

Bob: Break them apart? You mean into separate files?

Mark: Yes. That way we could have one file that displays a list of seats, one that displays the booking, and then include or call each page from the main page.

Laura: Oh - like separation of concerns.

Bob: What’s that?

Laura: Separation of concerns. It means you get one piece of code to do just one thing. Makes it easier to track down bugs.

Bob: Sure, sounds great... but how do you actually do that?

How can we split a page’s content up into separate files?

If we can split a page into separate files, it will make things more manageable. But how do we do that?

Rails lets us store fragments of pages into separate files called partial page templates or—more simply—partials. A partial is like a sub-routine that outputs a small part of a page. In our case we can use two partials: one for the seat list and another to add a new seat booking.

Partials are simply embedded Ruby files, just like templates. The only difference is that, unlike templates, partials have names that begin with an underscore (_).

ERb will ASSEMBLE our pages

We need to create partials for the booking form and the seat list, and then Embedded Ruby can process the flight page and call the partials each time the render expression is reached.

Note

This allows a separation of concerns: we have separate components dealing with booking and seats, and those components are combined for the user when needed.

To Do

Create a booking form partial

Add the booking form to the page

Create a seat list partial

Add the seat list to the page

image with no caption

When Rails gets a request for flight information, it will use the partials, templates, and layout with Embedded Ruby to generate a single HTML response.

Let’s start by looking at the first thing on the list—the booking form.

So how do we create the booking form partial?

Partials are just another kind of ERb file, so they contain the same kinds of tags that templates contain. Here’s the content of our _new_seat.html.erb partial. It contains exactly the same code as the new seat page, which means that all we have to do is copy app/views/seats/new.html.erb and save it as app/views/flights/_new_seat.html.erb:

image with no caption

We could have left the partial in the “seats” folder, but we move it into the “flights” folder to make it slightly easier to call. It’s also really important that the partial begins with the _ character. The _ character is used by Rails to distinguish partials from page templates.

Now we need to include the partial in the template

Creating the partial is only half the job. We now need to modify the flight show.html.erb page template to include the partial in its output. Partials, like templates, are really just pieces of Ruby code disguised to look like HTML. And in the same way that one piece of Ruby code can call another, the template can easily call the partial.

So how do you call a partial? By adding a render command to the flight page:

image with no caption

The render call tell Embedded Ruby to process the partial and include its output at that point in the file.

The partial should now appear in the flight page.

Watch it!

You don’t use the exact file name in the render call.

Even though partials begin with _ and end with .html.erb, both of these should be omitted when you call a partial with render.

We need to give the partial a seat!

The problem is caused because the ERb code contains a reference to the @seat variable. So why is this a problem?

This file used to be a page template associated with the SeatsController. The SeatsController initialized the @seat instance variable like this:

@seat = Seat.new
image with no caption

But now the file has become a partial that is going to be used by the FlightsController, and that controller has no @seat instance variable. So we need to change @seat into a local variable called seat:

image with no caption

seat is called a local variable because nothing outside the partial can read or write to it. But if that’s the case, then how do we pass the partial a value for the seat variable?

You can pass local variables to a partial

Partials and templates work a lot like Ruby methods or functions. When a template renders a partial, it’s a little like one function calling another function.

And since a partial’s like a function, you can pass in parameters like this:

image with no caption

The render method can accept a hash called locals. Within the hash, you can include a set of values indexed by a variable name. Like pretty much everywhere in Rails, names are expressed as symbols.

But what value should we pass in for seat? Let’s look at what value the original SeatsController used:

def new
  @seat = Seat.new

Because the form is being used to initialize a seat, we just need to pass the form a freshly created Seat object:

image with no caption

So has this fixed the problem with the flight page?

image with no caption

We can pass in the flight id number to the booking form partial.

But how will the form use the id? And how can a form provide a default value for a field, without asking the user?

Note

Looks like we haven’t completed this step yet after all there’s still more to do...

To Do

Create a booking form partial

Add the booking form to the page

Create a seat list partial

Add the seat list to the page

image with no caption

We also need a partial for the seat list

We can convert the seat “index” list in more or less the same way that we converted the booking form—by copying the original seat template file to a partial file. Let’s call this new partial _seat_list.html:

image with no caption

But the seat-list partial needs an array of seats

The seats “index” page displayed the contents of a SeatsController instance variable called @seats. The SeatsController created the instance variable just prior to index.html.erb was displayed. But what about now? We copied the index.html.erb template to a partial that will be displayed after running the FlightsController... so there’s no @seats instance variable containing an array of seats.

That means we need to provide the new _seat_list.html.erb partial with an array of seats. So what value should we provide for the array of seats? This is how the SeatsController initialized @seats:

def index
  @seats = Seat.find(:all)

So, for now, let’s call the seat list like this and see how it works:

image with no caption

People are ending up on the wrong flights

Everyone thinks the system looks great, so the system goes live. Unfortunately, it doesn’t takes long before someone spots a problem...

image with no caption

So what happened?

The flight page is displaying all the seat bookings for all the flights!

image with no caption

So what’s going on? The problem is caused by the render command, which calls the seat list partial. Remember, we called the partial like this:

<%= render :partial=>"seat_list",
      :locals=>{:seats=>Seat.find(:all)} %>

This displays the list of all seats in the database. That was fine when the seat list was the index page for the seat data... but now that we’re displaying the data against the flight, we need to restrict the seats so that only seats belonging to the current flight are displayed.

We could fix the finder... but it would be better to create a relationship.

A relationship connects models together

You’ll often find that certain model objects are often used together, like flights and seat bookings. You may need to use data from one type—like the flight id—to find the related objects in the other type, like the seats booked on the flight.

You could just use finders to read the related objects. For example, if you had a flight object called @flight, you could find the related seat objects like this:

image with no caption

But it’s actually easier to connect the two models together with a relationship:

image with no caption

A relationship makes objects of one type of object appear to be attributes of another type of object. For example, if we create a relationship on the flight model that connects to the seat model, we can refer to the seats associated with a flight like this:

@flight.seats

This will return the exact same thing as the finder above, but defining a relationship between two models will simplify your code and reduce the chances that you will make a mistake by repeatedly defining finders to jump from one model to another. It will also make your code a lot easier to read.

Sounds good. So how do relationships work?

But how do we define the relationship?

We are going to give the Flight model an extra attribute called seats, so it makes sense that the Flight model code is the place where we define the relationship:

image with no caption
image with no caption

The has_many command accepts the name of a related model and, because it will be used to find arrays of related seats, the name of the model is plural. So the parameter for has_many is :seats and not :seat (without the “s” at the end). Once the relationship is in place, you can use your new attribute like this:

image with no caption

The seats attribute returns an array of seat objects associated with the flight:

image with no caption

But some people have too much baggage

Now there’s a problem with the baggage on the flights. Some people are arriving at the airport carrying too much stuff—way more than the allowance for their flight. The flight data records the maximum baggage allowance, but a lot of the passengers are unhappy because they told the airline how much baggage they were bringing with them when they entered the seat booking, and the system didn’t complain. The system needs to be modified to prevent people reserving seats with too much baggage... before they show up with a booked seat.

image with no caption
image with no caption

We need to write our OWN validation

Rails comes with a set of built-in validators that can perform a lot of basic tests, like whether data is entered or if it is correctly formatted. But sometimes you will need to check something that isn’t covered by the basic validators.

In the case of baggage, Rails doesn’t come with a validates_too_much_baggage validator. There’s not a maximum value validator, either. So we need to write out own validator.

If you create a method in the Seat code called validate, that method will always be called by the model object just before things get saved or updated to the database:

image with no caption

The errors.add_to_base(...) command inserts a message into the list of errors. If there’s an error message created, the save or update operation is aborted and the user should be sent back to the form to correct the problem.

image with no caption

Prefer relationships over manual finders.

Instead of using finders to look up the related flight object, you can define a relationship between seats and flights. But the question is, what sort of relationship do we need?

When we created a relationship before, we gave the Flight model a new attribute called seats:

@flight.seats

But what do we need this time? Before, we had a Flight object and we wanted to know what the related seats were. The difference is that now we’re checking a seat object, and to do that we need to know about the related flight. So what sort of relationship do we need this time around?

We need the REVERSE relationship

This time we need a relationship that’s the opposite way around to the one we had before. Given a particular seat object, we need to get the related flight:

image with no caption

We want to have an attribute on seats like this:

@seat.flight

We want to know which flight a seat belongs to. And each seat will have only one flight. How do you think that will be coded?

So what does the Seat model look like now?

Let’s make the changes to the Seat model:

image with no caption
image with no caption

The system’s taken off at Coconut Airways

Life’s pretty good at the airline. Tourists and locals find it a breeze to use the system. The planes don’t get overloaded with baggage or get overbooked. In fact, the staff are using the time they saved a little more productively...

image with no caption
image with no caption

Tools for your Rails Toolbox

You’ve got Chapter 6 under your belt, and now you’ve added the ability to make the most of your connections.

Rails Tools

render :partial=>“name” displays _name.html.erb

Pass a variable to a partial with

render :partial=>“name”, :locals=>{:var1=>“val1”}

Custom validation code is in a model method called validate

errors.add_to_base(...) creates an error message

belongs_to defines a relationship from an object to its parent

has_many is the reverse relationship

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
34.204.196.206