Change is a fact of life—especially for data. So far you’ve seen how to whip up a quick Rails application with scaffolding, and how to write your own code to publish data from a database. But what if you want users to be able to edit data your way? What if scaffolding doesn’t do what you want? In this chapter, you’ll learn how to insert, update, and delete data in exactly the way you want. And while you’re doing that, you’ll be taken deeper into how Rails really works and maybe even learn a little about security along the way.
People love the MeBay site, but there’s a problem. Because MeBay was nervous about people having too much access to the data, sellers have to phone in details of their items to MeBay and wait while the system administrators create new ads for the sellers. As the number of people sending in ads has grown, so has the wait time. A lot of people are taking their business to other advertising sites now.
So MeBay has relented. After some discussion, they’ve decided that people should be allowed to post their own ads on the site using a page that looks like this:
The ads only go one way in the current application. The ad records are read from the database by the model, which converts them into ad objects that are then sent to the view by the controller. It works like this:
Saving data to the database is similar to publishing ads from the database, except it works the other way round. Instead of a page template to display an ad, you need a page template to submit an ad. Instead of a controller action method to send an ad to a page, you need a controller method to read data from a page and turn it into an object. And instead of the model reading a record and converting it into an object, you need the model to convert an object into a new record in the database.
You need a new page template to create the HTML form. Because it will be used to enter new ads, we’ll call this template new.html.erb
.
The “New ad” page will need to appear at
http://mebay.com/ads/new
and the form will be submitted to:
http://mebay.com/ads/create
So we also need to create a new route in routes.rb
.
Remember, a route is what tells Rails which pieces of code to use to satisfy a request from a browser.
The top of your config/routes.rb file should now look like this:
There are close relationships between many parts of a Rails application.
After all, the model contains the data for the application, the view allows the user to access that data, and the controller provides the logical glue that connects everything together.
But is there some special relationship between a form and a model?
Apart from the generated id, the fields in the form match the attributes of an ad object.
At some point the application is going to have to transfer data between the form and model. The name field will match to a name attribute, the description field will match to a description attribute and so on.
What if the model creates objects with default values in the attributes? Should the code that generates the default values in the form duplicate the model code?
After all, when the data in the form is received by the controller, should the form treat the fields as individual values? Or should all of the field values be associated together, like the attributes of an object?
Could Rails make use of the relationship between form fields and a model object when creating a form?
Rails can use a model object to help create a form. That means two things:
The values in the form fields will be set to the values stored in the attributes of the @ad
object. This doesn’t make a lot of difference to the ad form because new ads are blank.
The form fields will be given names that explicitly associate those fields with a model object.
So how can a name associate a field with an object? Let’s look at what it would mean for the ad form. This is the HTML that will be generated for a form that’s based on an Ad object:
Comparing the field names and their matching attributes, how do you think Rails will present the form data to the controller?
You have a few pages to think about this...
Field names | Object attributes |
|
|
|
|
|
|
|
|
|
|
|
|
The problem is caused by the @ad
object. By default, a variable like @ad
is set to a special value called nil
, which means no value. If @ad
is set to nil
, instead of being set to an Ad
object, it won’t have attributes like @ad.name
, @ad.description
, and so on.
If @ad
doesn’t have attributes, does that cause a problem for the form?
You bet! The form is based on the @ad
object, and the form accesses each of the object’s attributes to generate the initial values of the fields in the form. But as soon as the first attribute is called, nil
is returned, and that causes an error.
So how can we avoid this problem?
When the page with the form is generated, the initial values of each of the form fields will come from one of the attributes of the associated object.
The problem is that before the form can be generated, the new ad object needs to already exist. Of course, until the user completes the details of the ad, the object won’t be saved to the database—but even so, the object needs to be created before the page template is called.
The ads controller now has one method for each of the page template files. Rails will always call the controller method before generating a page from the page template.
It’s the combination of a controller method and a page template that make up an action. That’s why the action name appears in the name of the controller method and in name of the page template file.
An action is a controller method and a page template.
Now you have the controller creating the new ad object before the new.html.erb
page template runs, it’s time to see if the code works.
The form was generated using an Ad object. But what exactly does the form send back to the server when the form’s submitted?
Because the form uses HTTP, it can’t send the form object over the network. Instead it sends data.
Think back to how routing works. When a request arrives, Rails sends the details of the request to the routing system, which inserts values into a data structure called params[...]
, with values for the action and the controller.
The params[...]
data structure wasn’t created just for routing. It can also be used to store any data submitted to the application by a web form.
A form’s fields are recorded in the params[...]
table along with the name :ad
. Then, the value of the :ad
variable is actually another table of values, a table that maps a field name to a field value:
But what happens to this data when the controller receives it? And how can we actually USE this data?
Rails can only use objects to talk to the database, so before an ad can be saved to the database, you need to find some way to convert the form data into an Ad object.
How do you do that? Well, remember when you created a new blank Ad object to use with the form?
The Ad.new
method can also be called with a set of hash table of values that will be used to initialize the attributes of the new Ad object. And the form data just happens to be contained in a hash object:
The whole reason for converting the form data into an object was so you could save it.
How do you do that? With
@ad.save
When save is called on the model object, Rails inspects the attributes and generates a SQL insert statement to update the database:
With the save in place, the controller’s create method is complete:
def create @ad=Ad.new(params[:ad]) @ad.save end
HTTP works using pairs of requests and responses. For every request, there’s got to be a response.
When the controller’s create method completed, a new record was created successfully. Then Rails needed to generate a response page, so it looked for the page template that matched the current action. The current action was create
so it looked for create.html.erb
. But that template doesn’t exist!
So we need to write a create.html.erb page template. But what should the template have in it?
The users don’t want to see the intervening confirmation page generated by create.html.erb
. They just want to go straight to their ad. So what do you do?
You could edit the create.html.erb
page template so that it displays all of the new ad’s details, right? After all, the @ad
variable is visible from the page template and it contains all of the details of the new ad.
But that would be a bad idea. Why? Because that would mean that the create
.html.erb
page would be exactly the same as the show
.html.erb
page template you use to display each ad.
That’s duplication, and it would mean you had more code to maintain in the future. It would be much better if the create
action in the controller can choose to display the show.html.erb
page.
A controller method works together with a template to form an action. In all of the examples you’ve seen so far, both the controller method and the page template have been exclusively used for one action.
That’s why the controller methods and the page templates have included the action name somehow. When you were performing a show
action, you used the show
method in the ad controller and the show.html.erb
page template.
And we still want to use a controller method and a page template to complete the action, but now we want to be able to choose which page template gets called with the controller.
A controller action is a controller method and a page template.
We need a way for the controller to say that the output is found at a different URL.
A redirect is a special kind of response from the Rails application to the browser. A redirect tells the browser to go to a different URL for output. So even though the browser sent the form data to /ads/create
, a redirect sends the browser to ads/17
(for example, if 17 is the id number of the new ad).
When a new ad is created the browser automatically jumps to the new page.
Some users have made mistakes in their ad creations, and want to make changes to their ads. So they want more than just display and creation forms. Users now want to be able to edit their ads.
This means the system need to allow updates as well as inserts. Will that be difficult to do?
Even though the system can’t currently edit ads, will it be a lot of work to add an editing feature?
Think for a moment about the sequence the system goes through to insert an ad into the system:
A new blank ad object gets created and this is used to generate the ad input form.
The form is sent to the user, who updates the field values and submits the form back to the application
The data fields are converted back into an Ad object, which is saved to the database.
The user is forwarded to a page displaying the new ad.
Suppose you want to change a page. What would you expect to see? Maybe a form with the ad details that you can re-submit and have the changes saved, which is kind of the same sequence you used to create ads. Let’s look at the change sequence in more detail...
When someone edits an ad, they’ll use a form just like before. The user will change the details and the ad data will be saved. So just how similar is the change sequence to the creation sequence?
An existing ad is read from the database, and this ad will be used to generate the change form.
The form is sent to the user, who updates the field values and submits the form back to the application.
The data fields are converted back into an Ad object, which is used to update the database.
The user is forwarded to a page displaying the updated ad.
You can see that the sequence between the two operations barely differs at all. In the creation sequence, a new ad object is created and saved to the database. In the change sequence an existing ad is read, updated, and saved to the database.
So you need to make sure you take the differences between the two operations into account. You’ll need to keep track of things like the ad id number in the change sequence.
Do you think you could add an edit feature to the application as it stands?
The application can now create and update data. But that means anyone who can create an ad can update all ads. And that’s a problem.
The MeBay owners want anyone to be able to create ads, but they don’t want everybody else to be able to change the ad once it’s been posted.
One way of preventing just anyone changing ads is to protect the update function with a username and password.
The guys at MeBay have decided that only system administrators will be able to change ads. So they want the new update functionality secured with an admin username and password.
Fortunately, Rails makes it really easy to drop security right in. We’re going to use a special kind of web security called HTTP Authentication. This is the kind of security that pops up a dialog box and asks for a username and password when someone tries to enter a secure area of a web site.
The site is up and running, and everything’s going great, but after not too long there’s a problem: even after stuff gets sold, the ads stay there.
MeBay could use their own data entry systems to remove ads from the site, but they were so impressed by how simple the change function was, they’d like you to add a delete function to the website.
The feature will only be available to MeBay administrators, so they want the same security that was applied to the change functionality. Also, because there’s been a ton of spam as well as some spoof ads posted onto the site, they’d like to make the delete function easily available from the index page. That way they can remove inappropriate content with a single click.
Let’s look at what we need to do...
You can choose what functions are available.
You can add additional features like security.
And now you understand how to create code that inserts, updates and deletes data, you’ll be able to amend the code that scaffolding generates.
You’ve got Chapter 3 under your belt, and now you’ve added the ability to manually create applications that can insert, update and delete data.