CSC/ECE 517 Fall 2012/ch1b 1w38 nm
Introduction
This page is aimed at asserting the importance of Model View Controller Architecture and how MVC is implemented in Cookbook application.
Model View Controller (MVC) Architecture
The purpose of creating MVC architecture is to have clear separation between the responsibilities of business logic and representation. In a nutshell the responsibilities are mentioned as below:
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. Having described the responsibilities of three components let’s take a look how these components interact with each other.
Ruby on Rails
Ruby on Rails[2] is an MVC framework. Rails enforces a structure for application, it helps to knit 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[7]: Other frameworks use extensive code generation from scratch. Metaprogramming 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[6]: 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 which you can 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 very basic Ruby on Rails application. It is a great way to understand the nuances that ‘Ruby’ has to offer. It is built keeping in mind that the reader is new to the world of ruby and is developing his/her first Ruby on Rails application. The motive of this wiki page is to make the reader understand how the Cookbook application was built. And on the way to building this application, user will be introduced to some key concepts like Model, Views and Controllers. At the end of this page, reader must fairly be ready to start building his/her basic Ruby on Rails application.
A simple application to start -- Cookbook application
Start a new project. While creating a new project, remember to select Rails Application option as follows
When we create a Rails application, rails automatically help us create a brunch of files and folders as follows:
We mainly use the app directory. This is where the heart of the application is stored i.e. Models, Views and controllers.
Using scaffold to create framework
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 can create a scaffold by right clicking on the current Project->New->Run Rails Generator ->scaffold. This will open a dialog box to create a scaffold where we can enter the name and the attributes along with their data type. Once the scaffold has been created RubyMine will generate the model, view and controller files respectively. These files do not contain any specific methods but they provide a framework which is useful for general applications. The user can modify the MVC behavior according to his application requirements. Each scaffold has create, read, update and delete methods declared in the controller class. A user can modify the behavior of the controller through these methods.
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
In the Cookbook application we are considering a Cookbook which contains recipes for various dishes. The recipes have been divided into different categories, for example there is a recipe for ice tea which belongs to the category of beverages. There is a relationship which is created between a recipe and a category which can be expressed as an ice tea belongs to beverages. We can have many such recipes belonging to a single category, in this case many to one relationship is established.
Controllers for Cookbook Application
The Cookbook application starts by entering a URL in the browser which sends the request to the web server. Since our web server supports Rails application, it will hand over the control to a Rails controller. A controller ‘controls’ the entire application by responding to the incoming requests as needed. For example, consider the following URL http ://localhost:3000/categories/show which is decoded by the controller to know that its action named show has been invoked. All the controllers inherit only the ApplicationController which in turn inherits the ActionController::Base class. ActionContoller module provides the basic support for the controllers in Rails. In our Cookbook application we generate Categories controller using the scaffold mechanism which populates the controller with the basic methods such as index, create, show, new, edit, update and destroy. A simple example of index method is as follows:
def index @categories = Category.all respond_to do |format| format.html # index.html.erb format.json { render json: @categories } end end
In ruby, @ operator is used to declare instance variables. The index method makes use of @categories instance variable which is populated with all the records in the Category table. The respond_to method is used for responding to particular type of request. It contains a block and a variable |format| which determines whether the incoming request should be responded with HTML (when we are interacting with a user) or JSON format (if we are returning an object). After executing the code, the controller will render a template of the same name. (For this example, the template is views/categories/index.html.erb). Similarly the show method is defined as follows:
def show @category = Category.find(params[:id]) respond_to do |format| format.html # show.html.erb format.json { render json: @category } end end
The show method is used to display a particular object. The show method needs to access database to search for the given object. This is done using the find method which takes params[:id] as argument. The params[:id] contains the id or primary key of the object which is passed as a parameter to look up in the database and the result is stored in the instance variable. The result is displayed by rendering the show.html.erb file.
Active Record
In ruby, a program deals with objects which are mapped into database relational databases. This kind of mapping is facilitated by the Active Record.
Active Record maps the Rails classes in to database tables and rails objects into database records.
This makes it easy to perform actions on database tables by invoking the class methods. Another benefit of Active Record is database 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].
Method Pairs
There are couple of method pairs such as new-create and edit-update which are executed in sequence. In method pair one method is responsible for display preparation and the other method validates the data and attempts it to save in the database.
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
The new and create methods are known as method pairs which execute in sequence. The new method is used to instantiate a new Category object whereas create method is used to successfully save the object to the database. After execution of the new method, the controller renders the new.html.erb file which displays a form to create a new Category object. The user enters the required information in the form and the object id is returned in the URL on submitting the form. Consider the following URL is generated on submitting the form: http: //localhost:3000/categories/3. This means that Category object with id =3 will be created. The details of the object will be processed by the create method. The controller invokes the create method to store the object in the database. An observation can be made about the @category variable in the create method.
@category = Category.new(params[:category])
The parameters passed in the URL after creating a new object is stored in the params hash (denoted by params[]). In this case the params[] contains an object with category_id = 3. The params[] assigns the data of object with category_id =3 to the instance variable. The data entered in the instance variable is validated using the @category.save method. If no errors are found then the object stored in the database and a message on successful creation of the object is displayed to the user. However, if the data entered in the object contains an error, the user is redirected to the new.html.erb page to resubmit the information and the errors are displayed. Another example of such method pair is the edit and update methods. The edit method displays the table entry for an existing object, while the update method is invoked when the changes have been submitted.
Models of Cookbook Application
Model handles the data processing in a web application. Actions interact with the model to manipulate the data. Rails is capable of handling databases and this is facilitated by the model. You can base models on the Rails ActiveRecord module which contains support databases.
In our Cookbook application we have two models namely: Category and Recipe. They are represented by files category.rb and recipe.rb and each file is used to describe the relation between two classes. Let’s take a look at the two models
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
- one-to-one
class Syllabus < ActiveRecord::Base belongs_to :course¬ end class Course < ActiveRecord::Base has_one :syllabus end
- many-many
class Student < ActiveRecord::Base has_and_belongs_to_many :courses end class Course < ActiveRecord::Base has_and_belongs_to_many :students end
In Cookbook, we have many-one relationship between recipes and categories.
Views of Cookbook application
What is V in MVC? ‘V’ refers to views. Views are the actual interface that users can see. It is through views, that a user may request some data to be viewed or submits some data for processing.
Views in rails are of the form file_name.html.erb. Here erb means embedded ruby. What html.erb denotes is some ruby code is embedded in HTML code. Like HTML, embedded ruby also uses tags to represent data. However, to distinguish it from the HTML code, ruby is embedded between <% %> if some non-assignment ruby operation is to be performed. If something needs to be displayed on screen, <%= %> is used.
Views are present in “app/views” folder in the rails application.
Name of the view files is same as the name of their respective controller. For e.g.:- if I have a controller movie with an action new, the corresponding view page will be new.html.erb in the app/views/movie directory.
The Cookbook application views are as follows:
Recipe Controller Views
1. index.html.erb
<h1>Listing categories</h1> <table> <tr> <th>Name</th> <th></th> <th></th> <th></th> </tr> <% @categories.each do |category| %> <tr> <td><%= category.name %></td> <td><%= link_to 'Show', category %></td> <td><%= link_to 'Edit', edit_category_path(category) %></td> <td><%= link_to 'Destroy', category, method: :delete, data: { confirm: 'Are you sure?' } %></td> </tr> <% end %> </table> <br /> <%= link_to 'New Category', new_category_path %>
- <% @recipes.each do |recipe| %>
Here @recipes is an instance variable of model recipe defined in index action of the controller ‘recipes’. It contains all the recipes currently present in our application. Then each recipe is taken from this instance variable and is assigned to the local variable recipe. All the subsequent lines display the recipe details (name, description and instruction).
- <%= link_to ‘Show’, recipe %>
link_to is an ruby operator which is used to redirect the control to some other page. ‘Show’ is displayed as it is on the page and is an hyperlink. It links to a page pointed by recipe. The path is defined in the routes.rb file.
2. _form.html.erb
This is a partial. Partials are devices used for breaking the code in smaller, more manageable chunks. This helps in modularity and also maintain DRY principle[4]. With a partial, code for rendering a particular response is moved to its own file. So for e.g. we can use this same partial for creating and editing information, because the information involved is essentially the same.
<%= 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 :name %><br /> <%= f.text_field :name %> </div> <%= f.label :category %> <%= select('recipe','category_id', Category.all.collect {|c| [c[:name],c[:id]]}) %> <div class="field"> <%= f.label :description %><br /> <%= f.text_field :description %> </div> <div class="field"> <%= f.label :instructions %><br /> <%= f.text_area :instructions %> <%= f.submit %> </div> <% end %>
- <%= form_for(@recipe) do |f| %>
This statement means to render a form for the instance variable @recipe. If this page is being used for creating a new recipe, then the values for each key in the @recipe hash will be empty. If it is used for editing a recipe, @recipe will contain information corresponding to the recipe being edited.
- <% if @recipe.errors.any? %>
While saving an recipe or updating a recipe in the database, some violations may occur. Like the recipe name is left empty or its description is empty and so on. So while saving this information, database will signal some error. The errors are sent back to this page and are caught in @recipe.errors.any? statement.
- <%= select('recipe','category_id', Category.all.collect {|c| [c[:name],c[:id]]}) %>
This statement creates a dropdown box.
- ‘recipe’ : refers to the object to which the selected value is to be assigned. In this case , it is the recipe object
- ‘category_id’ : create a ‘category_id’ key in the recipe hash.
- Category.all.collect {|c| [c[:name],c[:id]]} : recover all categories from the database and list them in the dropdown box. The value for each name is c[:id]. Whenever a category is selected by the user, its corresponding id is assigned to the ‘category_id’ key in the ‘recipe’ object.
3. new.html.erb
This page is used to create a new recipe. This page simply renders the partial described above.
<h1>New recipe</h1> <%= render 'form' %> <%= link_to 'Back', recipes_path %>
- <%= render ‘form’ %>
This statement renders the partial.
4. edit.html.erb
This page is used to edit a existing recipe. This page also renders the partial.
<h1>Editing recipe</h1> <%= render 'form' %> <%= link_to 'Show', @recipe %> | <%= link_to 'Back', recipes_path %>
5. show.html.erb This page is used to show a particular recipe requested by the user. The recipe requested is captured by the controller in the @recipe instance variable and is passed to this view.
<p id="notice"><%= notice %></p> <p> <b>Name:</b> <%= @recipe.name %> </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 Category Views are very similar to the recipe Views and can be created similarly.
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
- [1] http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller
- [2] http://en.wikipedia.org/wiki/Ruby_on_Rails
- [3] Sam Ruby, Dave Thomas, David Heinemeier Hansson.agile web development with rails (fourth edition)
- [4] http://en.wikipedia.org/wiki/Don't_repeat_yourself
- [5] http://guides.rubyonrails.org/getting_started.html
- [6] http://api.rubyonrails.org/classes/ActiveRecord/Migration.html
- [7] http://en.wikipedia.org/wiki/Metaprogramming
- [8] http://net.tutsplus.com/tutorials/other/mvc-for-noobs/
- [9] Hotzner, S. (2007). Beginnning ruby on rail, Indianapolis: Wiley Publishing Inc
The old wiki can be found here http://expertiza.csc.ncsu.edu/wiki/index.php/CSC/ECE_517_Fall_2011/ch4_4d_ch.