Monday 21 July 2008

The Controller Part 2 - Basic Methods

Ruby on Rails uses the MVC archetecture for web applications. This post is the second of three posts about the controller part (part one here), looking at what goes inside the controller file.
Consider the usual scenario - a web log - with a model, called post, and a corresponding controller. The posts_controller file is where the PostsController class is defined. More specifically, this is where you put the methods that correspond to your actions. If you want a show action (responding to http://localhost:3000/posts/show/5), then you need a show method.

Actions that cause changes to the database table will generally require two methods. One to display the form that the user will make the changes on, and one that is invoked when the form is submitted, updating the table. The two standard sets are new and create, and edit and update. I will examine the edit and update that are generated by Rails in a typical web log application. The edit method produces a page with a form that the user can fill in. He hits Submit, and his HTTP request is pointed to the update method.
# GET /posts/1/edit
def edit
@post = Post.find(params[:id])
end


# PUT /posts/1
# PUT /posts/1.xml
def update
@post = Post.find(params[:id])
respond_to do format
if @post.update_attributes(params[:post])
flash[:notice] = 'Successfully updated.'
format.html { redirect_to(@post) }
format.xml { head :ok }
else
format.html { render :action => "edit" }
format.xml { render :xml => @post.errors,
:status => :unprocessable_entity }
end
end
end

Methods in the controller generally seem to revolve around collecting data, and then rendering it, and these are no exception (the edit method has an implicit render command). Let us look at them a little more closely.

Collecting Data
The edit method creates an instance variable @post. Its value is an object of the Post class with the relevant id. Any data that you want to display on your page must be put into instance variables at this point. If you want to populate a drop-down list with values from another table, you must assign those values to an instance variable here. If you want to welcome the user by name, you must assign the name to an instance variable here.

Numerous times this will involve searching a particular table, and Ruby on Rails offers a lot of help. The simple find method locates a record by id, but you can also search by any column
Post.find 12 # Gets the post with ID equal to 12
Post.find 2, 4, 5 # Gets the posts 2, 4 and 5
Post.find_all # Gets all the posts
Post.find_all :order => 'author', :offset => 20,
:limit => 10 # Gets the 21st through to 30th
# posts (or possibly 20 to 29), ordered by author
Post.find_by_title 'My First Blog Page'
# Gets the post with the given title
Post.find_all_by_author 'F2Andy'
# Gets an array of posts where the author is F2Andy
Post.find :all, conditions => {
:title => 'My First Blog Page', :author => 'F2Andy'}
# Finds all posts fulfilling both conditions
Post.find :all, conditions => [
'title LIKE :title AND author = :author', {
:title => 'Blog', :author => 'F2Andy'}]
# Finds all posts fulfilling the SQL conditions


Reference:
http://api.rubyonrails.org/classes/ActiveRecord/Base.html

See also this on find_by_sql
http://www.edwardthomson.com/blog/2007/02/complex_sql_queries_with_rails.html

By the way, named_scopes offer a way to get frequently used subsets of records from a model.

Turning to the update method, this is invoked when the user submits the form with the new data. The new values for the post are held in the hash, params[:post], and obviously we want to overwrite the old values. This is done with the update_attributes method in the Post class (actually the method is in the superclass). This saves the data to the database, returning true if successful (e.g., the data was validated successfully).

Rendering
This means we have two possible outcomes, and the resultant web page has to reflect that. The code also allows for an XML request, using the respond_to facility. The format goes into the variable format, and the rendering is determined by whether the update was successful, and whether the format should be HTML or XML. Here is the XML part from the show method:

format.xml { render :xml => @post.to_xml }

What this means is that what is returned to the web browser is the result of @post.to_xml. The to_xml method will automatically create an XML string for your model. This probably does not make much sense when your application goes beynd CRUD, and you either need to think carefully about what should be returned or not respond to XML at all.

Rails maintains a variable called flash for one response (redirectings are not counted). This is used to hold alerts, keeping the user informed. If the update was successful, this is therefore noted in flash[:notice]. Errors are reported in flash[:error]; why does Rails not do this by default?

More on responds_to:
http://weblog.jamisbuck.org/2006/3/27/web-services-rails-style

A successful update and an HTML response requested results in a redirect_to. The redirect_to method sends an HTTP 302 code to the requester, redirecting him to a new page. You can specify the controller and action for the redirect, but in this case the redirect is to @post, which Rails will handle as a request to show this post.

I will discuss rendering more in the next post.

Struggling with Ruby: Contents Page

No comments: