CSC/ECE 517 Fall 2011/ch4 4d ch

From Expertiza_Wiki
Jump to navigation Jump to search

Lecture 9: CookBook Application

Introduction

Model-view-controller (MVC)

Overview

Model-view-controller(MVC)[1] is an architectural pattern used in software engineering.In complex computer applications that present a large amount of data to the user, a developer often wishes to separate data (model) and user interface (view) concerns, so that changes to the user interface will not affect data handling, and that the data can be reorganized without changing the user interface.

The model-view-controller solves this problem by decoupling data access and business logic from data presentation and user interaction, by introducing an intermediate component: the controller.

MVC sequency

In a Rails application, an incoming request is first sent to a router, which works out where in the application the request should be sent and how the request itself should be parsed. Ultimately, this phase identifies a particular method (called an action in Rails parlance) somewhere in the controller code. The action might look at data in the request, it might interact with the model, and it might cause other actions to be invoked. Eventually the action prepares information for the view, which renders something to the user.

Ruby on Rails

Ruby on Rails[2] is an MVC framework. Rails enforces a structure for application, it helps to knits the models, views, and controllers as separate chunks of functionality while the program executes. No more external configuration needed. Rails is packed with features that make you more productive, with many of the following features building on one other.

Metaprogramming: Other frameworks use extensive code generation from scratch. Metaprogramming[9] techniques use programs to write programs. Ruby is one of the best languages for metaprogramming, and Rails uses this capability well. Rails also uses code generation but relies much more on metaprogramming for the heavy lifting.

Active Record: Rails introduces the Active Record framework, which saves objects to the database. The Rails version of Active Record discovers the columns in a database schema and automatically attaches them to your domain objects using metaprogramming.

Convention over configuration: Most web development frameworks for .NET or Java force you to write pages of configuration code. If you follow suggested naming conventions, Rails doesn't need much configuration.

Scaffolding: You often create temporary code in the early stages of development to help get an application up quickly and see how major components work together. Rails automatically creates much of the scaffolding you'll need.

Built-in testing: Rails creates simple automated tests you can then extend. Rails also provides supporting code called harnesses and fixtures that make test cases easier to write and run. Ruby can then execute all your automated tests with the rake utility.

Three environments: Rails gives you three default environments: development, testing, and production. Each behaves slightly differently, making your entire software development cycle easier. For example, Rails creates a fresh copy of the Test database for each test run.

Cookbook application

Cookbook application is a basic web application which demonstrates the fundamental way of developing web applications with Ruby on Rails. It is a good example for new developers. Lecture 9 focus on the controllers, models and views of Cookbook application. In this page chapter 2 will first introduce the steps of creating Cookbook application briefly, then part 3, 4 and 5 will analyse controllers, models and views of this application, introduce some important concepts at the same time. You can get an overview of Ruby on Rails application after reading this page.

A simple application to start -- Cookbook application

Start a new project by rails

When we start a ruby on rails application, the rails automatically help us create a brunch of files and folders as follows:

app/ config.ru doc/ lib/ public/ README test/ vendor/
config/ db/ Gemfile log/ Rakefile script/ tmp/

We will mainly use the app/ folder, it contains all the MVC components our project will need. Public/ folder will include some viewers for general purpose. For example the 404 error page, 500 error page, and the "Welcome Aboard" page you will see as an empty project when you open the website on your local machine: http://localhost:3000.

The test folder contains unit test, model test and encapsulate test for automatic generated MVCs, you will also need to create your own test file in this folder to test your code. Rakefile and Gemfile contains many configuration about the environment your Ruby on Rails are running, you will need to change this files to make your project run on certain environment or when you need to deploy your work on Heroku. db/ folder contains migrate file. In config folder, you will need to config the route.rb to set your mainpage, and make changes to certain evens actions.

So much brief introduction for the structure of the files, we will talk about files' functionality in details in following chapter.

Using sccaffold to create framework

Scaffolding

Rails scaffolding is a quick way to generate some of the major pieces of an application. If you want to create the models, views, and controllers for a new resource in a single operation, scaffolding is the tool for the job.

In Rubymine, we use Generate -> scaffold to create a MVC structure. Like, we want to create a Categories MVC, a category can be created, edit, display and destroy. We want the categroy to have a name. So we give scaffold:

Categories  name:string 

Then the scaffold will automatically create files.

invoke active_record
create db/migrate/20111018000001_create_categories.rb
create app/models/ category.rb
invoke test_unit
create test/unit/ category _test.rb
create test/fixtures/ categories.yml
route resources : categories
invoke scaffold_controller
create app/controllers/ categories_controller.rb
invoke erb
create app/views/ categories
create app/views/ categories /index.html.erb
create app/views/ categories /edit.html.erb
create app/views/ categories /show.html.erb
create app/views/ categories /new.html.erb
create app/views/ categories /_form.html.erb
invoke test_unit
create test/functional/ categories _controller_test.rb
invoke helper
create app/helpers/ categories _helper.rb
invoke test_unit
create test/unit/helpers/ categories _helper_test.rb
invoke stylesheets
create public/stylesheets/scaffold.css

Working without scaffold

Or you don't want to create a fully standard framework with scaffold, then you can use RubyMine generate command, you can create merely model, controller and view using generate command, there are many command included in the generate that you can use, expect those MVC, you can also crate migration, assets, intergration_test and performance_test. Or you don't want to create a empty controller and writing code all yourself you can also use scaffold_controller to create controller

Here is an example. You have create the Model and everything on your own without using the scaffolding in Ruby on Rails. You create the following class:

class Dog < ActiveRecord::Base 

end 

You can add a table Dogs. by

rails g migration CreateDogs

which also a generate command

Database migrate and run

ActiveRecord Migration

Migrations can manage the evolution of a schema used by several physical databases. It’s a solution to the common problem of adding a field to make a new feature work in your local database, but being unsure of how to push that change to other developers and to the production server. With migrations, you can describe the transformations in self-contained classes that can be checked into version control systems and executed against another database that might be one, two, or five versions behind.You can find a lot more details about rails migration here[8].

Then we apply rake: db: migrate to migrate data to database. Till now we have created a simple test MVC structure category with the name. Categories can be created as an object, each category object has a name. This name is correspond to the item in database.

Use the same procedure we create the recipe.

Recipe  Title:string  Description:string Instructions:text

And too, we use migrate for Recipe.

Controllers of Cookbook application

Now we took a detailed look at categories_controller.rb. It controls how user actions are interpreted, act as the input mechanism for the views and models

Class CategoriesController< ApplicationController

Obviously, the CategoriesController inherits from ApplicationController, so we want to take a look at ApplicationController, which is also in the Controller folder:

class ApplicationController < ActionController::Base
  protect_from_forgery
end

So we can see that only one sentence is included in ApplicationController, the main function that CategoriesController inherits is from the ActionController::Base. The ApplicationController offers a convenient way to modify some properties for all Controllers without change the Base file.

Inside the CategoriesController defined all methods that would be used, they are: index, show, new, edit, create, update, and destroy. We want to first take a look at the index method:

  def index
    @categories = Category.all
    respond_to do |format|
      format.html # index.html.erb
      format.json { render json: @categories }
    end
  end

In ruby, @ means create an instance inside method, Category.all means return all the categories, and give the array to @categories instance variable. The definition of respond_to method can be found in mime_responds.rb. It follows with a block with a variable |format|, this segment of code determines whether to respond with HTML (as when we are interacting with a user) or JSON format (if we are returning an object). The response format is determined by HTTP Accept header sent by the client.

After we executing this method, the controller will render a template of that method, this template is in the views folder. (For this example, the template is views/categories/index/html.erb)

Since we have known the function of respond_to method and its block, we'll take a look at what's different betweent different methods. Now we take a look at show method, we have:

@category = Category.find (params [: id])

We mentioned that the find method has been applied to the category object with a parameter of : id. The find method does a database access. The parameter : all (as we have seen in the last method index) told it to retrieve all records from the category table and assign the collection to a variable called @categories, while in the show method, the params is an object that holds all of the parameters passed in a browser request, params[: id] holds the id, or primary key of the object. The current object id is passed through controller as a parameter, and the show will display that specify category with id passed.

From the two methods we mentioned above we can see that Ruby on Rauls deal with objects, and it map objects into relational databases with Active Record. In Active Record, database tables correspond to Rails classes, and database records(rows) correspond to Rails objects.

Method pairs

In the class the professor has mentioned a coulple of method pairs. We have new and create, edit and update. In these kind of method pairs, one method is responsible for the display preparation; and the other method processes the data that was entered and attempts to save it to the database.

For example, the new set a table entry and display it in a window, when the object is created in the database, the create is invoked, create rendering pages and redirect users to other pages.

  def new
    @category = Category.new
    respond_to do |format|
      format.html # new.html.erb
      format.json { render json: @category }
    end
  end
  def create
    @category = Category.new(params[:category])
    respond_to do |format|
      if @category.save
        format.html { redirect_to @category, notice: 'Category was successfully created.' }
        format.json { render json: @category, status: :created, location: @category }
      else
        format.html { render action: "new" }
        format.json { render json: @category.errors, status: :unprocessable_entity }
      end
    end
  end

Similarly, for edit and update method pairs. Edit retrieves a table entry and displays it in a window, while the changes are submitted, update is invoked.

Models of Cookbook application

There are only two files in the model, one each for the tables in the application. Let's take a look at them. The files in model are used to describe relationship between classes.

category.rb

class Category < ActiveRecord::Base
  has_many :recipes
end

recipe.rb

class Recipe < ActiveRecord::Base
  belongs_to :category
  validates :title, :presence => true
  validates :instructions, :presence => true
  validates :category, :presence => true
end

Relationships in models

A relationship may be one-to-one (e.g. a course has a syllabus and a syllabus belongs to one course)

class Syllabus < ActiveRecord::Base
	belongs_to :course­
end

class Course < ActiveRecord::Base
	has_one :syllabus
end 

one-to-many (e.g. a course has many assignments)


class Assignment < ActiveRecord::Base
	belongs_to :course
end

class Course < ActiveRecord::Base
	has_many :assignments
end

many-to-many (e.g. a course has many students; students have many courses)

class Student < ActiveRecord::Base
	has_and_belongs_to_many :courses
end

class Course < ActiveRecord::Base
	has_and_belongs_to_many :students
end

The relationship between recipes and categories is one to many.It is represented in the db:migrate files that define the classes recipes and categories.

Views of Cookbook application

Files in view folder defines the titles shown in a certain page, and superlink that are refered to.

edit.html.erb

<h1>Editing category</h1>
<%= render 'form' %>
<%= link_to 'Show', @category %> |
<%= link_to 'Back', categories_path %>

The 'form' refers to a partial named _form.html.erb. To stick to the DRY[6](Don't repeat yourself) principles. With partials, we can move the code for rendering a particular piece of a response to its own file so modularity can be achieved.

<%= form_for(@category) do |f| %>
  <% if @category.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(@category.errors.count, "error") %> prohibited this category from being saved:</h2>
      <ul>_form.html.erb for category
      <% @category.errors.full_messages.each do |msg| %>
        <li><%= msg %></li>
      <% end %>
      </ul>
    </div>
  <% end %>

  <div class="field">
    <%= f.label :name %><br />
    <%= f.text_field :name %>
  </div>

  <div class="actions">
    <%= f.submit %>
  </div>
<% end %>

_form.html.erb for recipe

<%= form_for(@recipe) do |f| %>
  <% if @recipe.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(@recipe.errors.count, "error") %> prohibited this recipe from being saved:</h2>
      <ul>
      <% @recipe.errors.full_messages.each do |msg| %>
        <li><%= msg %></li>
      <% end %>
      </ul>
    </div>
  <% end %>

  <div class="field">
    <%= f.label :title %><br />
    <%= f.text_field :title %>
  </div>

  <div class="field">
    <%= f.label :category %><br />
    <%= select("recipe", "category_id", Category.find(:all).collect{ |c| [ c.name, c.id] }) %>
  </div>

  <div class="field">
    <%= f.label :description %><br />
    <%= f.text_field :description %>
  </div>

  <div class="field">
    <%= f.label :instructions %><br />
    <%= f.text_area :instructions %>
  </div>

  <div class="actions">
    <%= f.submit %>
  </div>
<% end %>

What's the difference between above two files named _form.html.erb? The _form.html.erb for recipes handles more data from response of recipes.url than categories hence its bigger than that for categories.

show.html.erb

<p>
  <b>Name:</b>
  <%=h @category.name %>
</p>
<%= link_to 'Edit', edit_category_path(@category) %> |
<%= link_to 'Back', categories_path %>
<p id="notice"><%= notice %></p>
<p>
 <b>Title:</b>
  <%= @recipe.title %>
</p>
<p>
 <b>Description:</b>
  <%= @recipe.description %>
</p>
<p>
  <b>Instructions:</b>
  <%= @recipe.instructions %>
</p>

<%= link_to 'Edit', edit_recipe_path(@recipe) %> |
<%= link_to 'Back', recipes_path %>

The view file show.html.erb describes in the front page, what link will be invoked by clicking different buttons, what titles are shown in this page.

new.html.erb

<h1>New category</h1>
<%= render 'form' %>
<%= link_to 'Back', categories_path %>

Category controller will be invoked when the form is submitted. this file new.html.erb describe how the new page will be when click new category button.

Conclusion

MVC was first proposed by Trygve Reenskaug in 1979. He broke applications into three types of components: models, which is responsible for maintaining the state of the application; views, which are responsible for generating a user interface; and controllers, which orchestrate the applications by receiving events from outside world, interact with model, and display an appropriate view to user. This separation of concerns led to far less computing, and in turn made the code easier to write and maintain.

Use Ruby on Rails can implement MVC to build various types of web applications. The Cookbook is an easy starter. Readers can learn the basics of Ruby on Rails from this page.

References