CSC/ECE 517 Fall 2011/ch4 4d ch: Difference between revisions
(66 intermediate revisions by 2 users not shown) | |||
Line 1: | Line 1: | ||
'''Lecture 9: CookBook Application''' | '''Lecture 9: CookBook Application''' | ||
= | =Introduction= | ||
==Model-view-controller (MVC)== | ==Model-view-controller (MVC)== | ||
Model-view-controller (MVC) 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. | ===Overview=== | ||
[http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller 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. | 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. | ||
[[File:MVC_Diagram_3.jpg]] | |||
===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== | ||
Ruby on Rails 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. | |||
[[File:RoR.png]] | |||
[http://en.wikipedia.org/wiki/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. | |||
<b>Metaprogramming</b>: Other frameworks use extensive code generation from scratch. [http://en.wikipedia.org/wiki/Metaprogramming 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. | |||
<b>Active Record</b>: 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. | |||
<b>Convention over configuration</b>: 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. | |||
<b>Scaffolding</b>: 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. | |||
<b>Built-in testing</b>: 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. | |||
<b>Three environments</b>: 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== | ||
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 | 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 | ==Start a new project by rails== | ||
[[File:rubylc1.jpg]] | [[File:rubylc1.jpg]] | ||
Line 22: | Line 43: | ||
<pre> | <pre> | ||
app/ config.ru doc/ lib/ public/ README test/ vendor/ | app/ config.ru doc/ lib/ public/ README test/ vendor/ | ||
config/ db/ Gemfile log/ Rakefile script/ tmp/ | config/ db/ Gemfile log/ Rakefile script/ tmp/ | ||
</pre> | </pre> | ||
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 | 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. | ||
[[File:rubylc2.jpg]] | [[File:rubylc2.jpg]] | ||
Line 32: | Line 52: | ||
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. | 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 | 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: | 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: | ||
Line 39: | Line 64: | ||
<pre> | <pre> | ||
Categories name:string | Categories name:string | ||
</pre> | |||
Then the scaffold will automatically create files. | |||
Then the scaffold will automatically create files | |||
<pre> | <pre> | ||
invoke active_record | invoke active_record | ||
create db/migrate/20111018000001_create_categories.rb | create db/migrate/20111018000001_create_categories.rb | ||
create app/models/ category.rb | create app/models/ category.rb | ||
invoke test_unit | invoke test_unit | ||
create test/unit/ category _test.rb | create test/unit/ category _test.rb | ||
create test/fixtures/ categories.yml | create test/fixtures/ categories.yml | ||
route resources : categories | route resources : categories | ||
invoke scaffold_controller | invoke scaffold_controller | ||
create app/controllers/ categories_controller.rb | create app/controllers/ categories_controller.rb | ||
invoke erb | invoke erb | ||
create app/views/ categories | create app/views/ categories | ||
create app/views/ categories /index.html.erb | create app/views/ categories /index.html.erb | ||
create app/views/ categories /edit.html.erb | create app/views/ categories /edit.html.erb | ||
create app/views/ categories /show.html.erb | create app/views/ categories /show.html.erb | ||
create app/views/ categories /new.html.erb | create app/views/ categories /new.html.erb | ||
create app/views/ categories /_form.html.erb | create app/views/ categories /_form.html.erb | ||
invoke test_unit | invoke test_unit | ||
create test/functional/ categories _controller_test.rb | create test/functional/ categories _controller_test.rb | ||
invoke helper | invoke helper | ||
create app/helpers/ categories _helper.rb | create app/helpers/ categories _helper.rb | ||
invoke test_unit | |||
create test/unit/helpers/ categories _helper_test.rb | |||
invoke stylesheets | |||
create public/stylesheets/scaffold.css | |||
</pre> | |||
===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 | |||
create | 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: | ||
<pre> | |||
class Dog < ActiveRecord::Base | |||
end | |||
</pre> | |||
You can add a table Dogs. by | |||
<pre> | |||
rails g migration CreateDogs | |||
</pre> | </pre> | ||
which also a generate command | |||
==Database migrate and run== | |||
<b>ActiveRecord Migration</b> | |||
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 [http://api.rubyonrails.org/classes/ActiveRecord/Migration.html 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. | 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. | ||
[[File:rubylc4.jpg]] | [[File:rubylc4.jpg]] | ||
Use the same procedure we create the recipe | Use the same procedure we create the recipe. | ||
<pre> | <pre> | ||
Recipe Title:string Description:string Instructions:text | Recipe Title:string Description:string Instructions:text | ||
</pre> | </pre> | ||
Line 114: | Line 134: | ||
And too, we use migrate for Recipe. | 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 | 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 | ||
<pre> | <pre> | ||
Class CategoriesController< ApplicationController | Class CategoriesController< ApplicationController | ||
</pre> | </pre> | ||
Obviously, the CategoriesController inherits from ApplicationController, so we want to take a look at ApplicationController, which is also in the Controller folder: | Obviously, the CategoriesController inherits from ApplicationController, so we want to take a look at ApplicationController, which is also in the Controller folder: | ||
<pre> | <pre> | ||
class ApplicationController < ActionController::Base | class ApplicationController < ActionController::Base | ||
protect_from_forgery | protect_from_forgery | ||
end | end | ||
</pre> | </pre> | ||
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. | 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: | 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: | ||
<pre> | <pre> | ||
def index | def index | ||
@categories = Category.all | @categories = Category.all | ||
respond_to do |format| | respond_to do |format| | ||
format.html # index.html.erb | format.html # index.html.erb | ||
format.json { render json: @categories } | format.json { render json: @categories } | ||
end | end | ||
end | end | ||
</pre> | </pre> | ||
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. | 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: | |||
Since we have known the function of respond_to method and its block, | |||
<pre> | <pre> | ||
@category = Category.find (params [: id]) | @category = Category.find (params [: id]) | ||
</pre> | </pre> | ||
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. | 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. | 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. | ||
<b>Method pairs</b> | |||
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. | 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. | |||
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 | |||
<pre> | <pre> | ||
def new | def new | ||
@category = Category.new | @category = Category.new | ||
respond_to do |format| | respond_to do |format| | ||
format.html # new.html.erb | format.html # new.html.erb | ||
format.json { render json: @category } | format.json { render json: @category } | ||
end | end | ||
end | end | ||
def create | def create | ||
@category = Category.new(params[:category]) | @category = Category.new(params[:category]) | ||
respond_to do |format| | respond_to do |format| | ||
if @category.save | if @category.save | ||
format.html { redirect_to @category, notice: 'Category was successfully created.' } | format.html { redirect_to @category, notice: 'Category was successfully created.' } | ||
format.json { render json: @category, status: :created, location: @category } | format.json { render json: @category, status: :created, location: @category } | ||
else | else | ||
format.html { render action: "new" } | format.html { render action: "new" } | ||
format.json { render json: @category.errors, status: :unprocessable_entity } | format.json { render json: @category.errors, status: :unprocessable_entity } | ||
end | end | ||
end | end | ||
end | end | ||
</pre> | </pre> | ||
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. | 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. | |||
= | |||
There are only two files in the model, one each for the tables in the application. | |||
category.rb | category.rb | ||
<pre> | <pre> | ||
class Category < ActiveRecord::Base | class Category < ActiveRecord::Base | ||
has_many :recipes | has_many :recipes | ||
end | end | ||
</pre> | |||
recipe.rb | recipe.rb | ||
<pre> | |||
class Recipe < ActiveRecord::Base | class Recipe < ActiveRecord::Base | ||
belongs_to :category | belongs_to :category | ||
validates :title, :presence => true | validates :title, :presence => true | ||
validates :instructions, :presence => true | validates :instructions, :presence => true | ||
validates :category, :presence => true | validates :category, :presence => true | ||
end | end | ||
</pre> | </pre> | ||
<b>Relationships in models</b> | |||
A relationship may be | A relationship may be | ||
<b>one-to-one</b> (e.g. a course has a syllabus and a syllabus belongs to one course) | |||
<pre> | <pre> | ||
class Syllabus < ActiveRecord::Base | class Syllabus < ActiveRecord::Base | ||
belongs_to :course | |||
belongs_to : | |||
end | end | ||
class Course < ActiveRecord::Base | class Course < ActiveRecord::Base | ||
has_one :syllabus | |||
has_one :syllabus | |||
end | end | ||
</pre> | </pre> | ||
<b>one-to-many</b> (e.g. a course has many assignments) | |||
<pre> | <pre> | ||
class Assignment < ActiveRecord::Base | class Assignment < ActiveRecord::Base | ||
belongs_to :course | belongs_to :course | ||
end | end | ||
class Course < ActiveRecord::Base | class Course < ActiveRecord::Base | ||
has_many :assignments | |||
has_many :assignments | |||
end | end | ||
</pre> | </pre> | ||
<b>many-to-many</b> (e.g. a course has many students; students have many courses) | |||
<pre> | <pre> | ||
class Student < ActiveRecord::Base | class Student < ActiveRecord::Base | ||
has_and_belongs_to_many :courses | has_and_belongs_to_many :courses | ||
end | end | ||
class Course < ActiveRecord::Base | class Course < ActiveRecord::Base | ||
has_and_belongs_to_many :students | |||
has_and_belongs_to_many :students | |||
end | end | ||
</pre> | </pre> | ||
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 | |||
<pre> | <pre> | ||
<h1>Editing category</h1> | <h1>Editing category</h1> | ||
<%= render 'form' %> | <%= render 'form' %> | ||
<%= link_to 'Show', @category %> | | <%= link_to 'Show', @category %> | | ||
<%= link_to 'Back', categories_path %> | <%= link_to 'Back', categories_path %> | ||
</pre> | </pre> | ||
The 'form' refers to a partial named _form.html.erb. To stick to the [http://en.wikipedia.org/wiki/Don't_repeat_yourself 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. | |||
The form refers to a partial named _form.html.erb. To stick to the DRY(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. | |||
<pre> | <pre> | ||
<%= form_for(@category) do |f| %> | <%= form_for(@category) do |f| %> | ||
<% if @category.errors.any? %> | <% if @category.errors.any? %> | ||
<div id="error_explanation"> | <div id="error_explanation"> | ||
<h2><%= pluralize(@category.errors.count, "error") %> prohibited this category from being saved:</h2> | <h2><%= pluralize(@category.errors.count, "error") %> prohibited this category from being saved:</h2> | ||
<ul>_form.html.erb for category | |||
<ul> | |||
<% @category.errors.full_messages.each do |msg| %> | <% @category.errors.full_messages.each do |msg| %> | ||
<li><%= msg %></li> | <li><%= msg %></li> | ||
<% end %> | <% end %> | ||
</ul> | </ul> | ||
</div> | </div> | ||
<% end %> | <% end %> | ||
<div class="field"> | <div class="field"> | ||
<%= f.label :name %><br /> | <%= f.label :name %><br /> | ||
<%= f.text_field :name %> | <%= f.text_field :name %> | ||
</div> | </div> | ||
<div class="actions"> | <div class="actions"> | ||
<%= f.submit %> | <%= f.submit %> | ||
</div> | </div> | ||
<% end %> | <% end %> | ||
</pre> | |||
_form.html.erb for recipe | |||
<pre> | |||
<%= form_for(@recipe) do |f| %> | <%= form_for(@recipe) do |f| %> | ||
<% if @recipe.errors.any? %> | <% if @recipe.errors.any? %> | ||
<div id="error_explanation"> | <div id="error_explanation"> | ||
<h2><%= pluralize(@recipe.errors.count, "error") %> prohibited this recipe from being saved:</h2> | <h2><%= pluralize(@recipe.errors.count, "error") %> prohibited this recipe from being saved:</h2> | ||
<ul> | <ul> | ||
<% @recipe.errors.full_messages.each do |msg| %> | <% @recipe.errors.full_messages.each do |msg| %> | ||
<li><%= msg %></li> | <li><%= msg %></li> | ||
<% end %> | <% end %> | ||
</ul> | </ul> | ||
</div> | </div> | ||
<% end %> | <% end %> | ||
<div class="field"> | <div class="field"> | ||
<%= f.label :title %><br /> | <%= f.label :title %><br /> | ||
<%= f.text_field :title %> | <%= f.text_field :title %> | ||
</div> | </div> | ||
<div class="field"> | <div class="field"> | ||
<%= f.label :category %><br /> | <%= f.label :category %><br /> | ||
<%= select("recipe", "category_id", Category.find(:all).collect{ |c| [ c.name, c.id] }) %> | <%= select("recipe", "category_id", Category.find(:all).collect{ |c| [ c.name, c.id] }) %> | ||
</div> | </div> | ||
<div class="field"> | <div class="field"> | ||
<%= f.label :description %><br /> | <%= f.label :description %><br /> | ||
<%= f.text_field :description %> | <%= f.text_field :description %> | ||
</div> | </div> | ||
<div class="field"> | <div class="field"> | ||
<%= f.label :instructions %><br /> | <%= f.label :instructions %><br /> | ||
<%= f.text_area :instructions %> | <%= f.text_area :instructions %> | ||
</div> | </div> | ||
<div class="actions"> | <div class="actions"> | ||
<%= f.submit %> | <%= f.submit %> | ||
</div> | </div> | ||
<% end %> | <% end %> | ||
</pre> | </pre> | ||
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. | |||
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 | show.html.erb | ||
<pre> | <pre> | ||
<p> | <p> | ||
<b>Name:</b> | <b>Name:</b> | ||
<%=h @category.name %> | <%=h @category.name %> | ||
</p> | </p> | ||
<%= link_to 'Edit', edit_category_path(@category) %> | | <%= link_to 'Edit', edit_category_path(@category) %> | | ||
<%= link_to 'Back', categories_path %> | <%= link_to 'Back', categories_path %> | ||
<p id="notice"><%= notice %></p> | <p id="notice"><%= notice %></p> | ||
<p> | <p> | ||
<b>Title:</b> | |||
<%= @recipe.title %> | <%= @recipe.title %> | ||
</p> | </p> | ||
<p> | <p> | ||
<b>Description:</b> | |||
<%= @recipe.description %> | <%= @recipe.description %> | ||
</p> | </p> | ||
<p> | <p> | ||
<b>Instructions:</b> | <b>Instructions:</b> | ||
<%= @recipe.instructions %> | <%= @recipe.instructions %> | ||
</p> | </p> | ||
<%= link_to 'Edit', edit_recipe_path(@recipe) %> | | <%= link_to 'Edit', edit_recipe_path(@recipe) %> | | ||
<%= link_to 'Back', recipes_path %> | <%= link_to 'Back', recipes_path %> | ||
</pre> | </pre> | ||
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 | new.html.erb | ||
<pre> | <pre> | ||
<h1>New category</h1> | <h1>New category</h1> | ||
<%= render 'form' %> | <%= render 'form' %> | ||
<%= link_to 'Back', categories_path %> | <%= link_to 'Back', categories_path %> | ||
</pre> | </pre> | ||
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. | 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/Integrated_development_environment | |||
= | *[2]: http://en.wikipedia.org/wiki/Ruby_on_Rails | ||
*[3]: http://courses.ncsu.edu/csc517//common/lectures/notes/lec9.pdf | |||
*[4]: http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller | |||
*[5]: Sam Ruby, Dave Thomas, David Heinemeier Hansson.agile web development with rails (fourth edition) | |||
[ | *[6]: http://en.wikipedia.org/wiki/Don't_repeat_yourself | ||
*[7]: http://guides.rubyonrails.org/getting_started.html | |||
*[8]: http://api.rubyonrails.org/classes/ActiveRecord/Migration.html | |||
*[9]: http://en.wikipedia.org/wiki/Metaprogramming | |||
[ | |||
[ |
Latest revision as of 03:14, 3 November 2011
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
- [1]: http://en.wikipedia.org/wiki/Integrated_development_environment
- [2]: http://en.wikipedia.org/wiki/Ruby_on_Rails
- [3]: http://courses.ncsu.edu/csc517//common/lectures/notes/lec9.pdf
- [4]: http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller
- [5]: Sam Ruby, Dave Thomas, David Heinemeier Hansson.agile web development with rails (fourth edition)
- [6]: http://en.wikipedia.org/wiki/Don't_repeat_yourself
- [7]: http://guides.rubyonrails.org/getting_started.html
- [8]: http://api.rubyonrails.org/classes/ActiveRecord/Migration.html
- [9]: http://en.wikipedia.org/wiki/Metaprogramming