Introduction
In this article, we will explore the capabilities of Ruby with respect to the Data Tier. One can understand the power of Ruby which greatly simplifies the development of data aware applications after reading this article. This is mainly because of the abstraction introduced in Ruby in the form of Active Record. Active Record defines the object relational mapping between Ruby objects and Database tables. The first section of this article will deal with Active Record and how to work with CRUD operations using it. The later section of the article guides in developing a full-blown web application using the Active Record API.
Active Record Basics
There are various implementations for Object Relation mapping model available in Ruby. One such popular implementation is Active Record. Usage of Active Record API in an application will lead to writing fewer lines of code. Active Record also provides various utility classes for directly creating tables from the application using simpler syntax. The mapping between Ruby classes and database tables can be elegantly done. Active Record follows the convention of having plaral names for the table name and singular name for the class names. For example, one can say that the class ‘Employee’ will map directly to ’employees’ table in Active Record context. It is also possible to dynamically generate method names through Active Record, the example of which will be seen later in this article.
Creating records
In this section, we will see the basics of using Active Record API. More specifically we will see how to map Ruby classes and database table for creating records in the database. Note that this example using MySql database, so make sure before using the application, a compatible version of MySql gem is installed in your machine.
require "logger" require "rubygems" require "active_record" require "pp" ActiveRecord::Base.logger = Logger.new(STDOUT) ActiveRecord::Base.establish_connection(:adapter => "mysql" , :database => "ruby", :username => "root", :password => "XXX") class Bank < ActiveRecord::Base end Bank.delete_all hdfc_bank = Bank.new(); hdfc_bank.id = '1'; hdfc_bank.name = "HDFC Bank"; hdfc_bank.operation_date = Date.today; hdfc_bank.head_office = "Mumbai"; hdfc_bank.save; puts ("HDFC Bank object created"); sbi_bank = Bank.new(); sbi_bank.id = '2'; sbi_bank.name = "SBI Bank"; sbi_bank.operation_date = Date.today; sbi_bank.head_office = "Bangalore"; sbi_bank.save; puts ("SBI Bank object created"); icici_bank = Bank.new(); icici_bank.id = '3'; icici_bank.name = "ICICI Bank"; icici_bank.operation_date = Date.today; icici_bank.head_office = "Delhi"; icici_bank.save; puts ("ICICI Bank object created");
The first few statements import the necessary dependencies packages such as ‘logger’ and ‘active_record’. We have defined a logger that points to the standard console. A connection is established to the MySql database using the method call ‘establish_connection’ by passing in the database adapter name, the database name and username/password details. It is assumed that a table with the name ‘banks’ exist in the database for this example. Next we have defined a class called ‘Bank’ which extends from ‘ActiveRecord::Base’. This single line will ensure that a mapping is established between the Ruby class ‘Bank’ and the database table ‘banks’.
Since the Bank class extends from ‘ActiveRecord::Base’, most of the common database CRUD operations become implicitly available to the Bank class. One such operation is delete_all() which will remove all the bank records from the database. Note that the banks table has the columns ‘ID’, ‘NAME’, ‘OPERATION_DATE’ and ‘HEAD_OFFICE’ which will directly map to the properties ‘id’, ‘name’, ‘operation_date’ and ‘head_office’ of the Bank class.
A new empty bank record is created using the statement Bank.new(), after that we initialize various properties of the bank object. A call to save() on the Bank object will persist the entity to the database.
Finding Records
In the previous section, we have seen how to use the Active Record API for inserting data into the database. In this section, we will see how to find data using easy-to-use predefined data-aware methods.
require "logger" require "rubygems" require "active_record" require "pp" ActiveRecord::Base.logger = Logger.new(STDOUT) ActiveRecord::Base.establish_connection(:adapter => "mysql" , :database => "ruby", :username => "root", :password => "XXX") class Bank < ActiveRecord::Base end bank_object = Bank.find(:first) puts ("Id is #{bank_object.id}"); puts ("Name is #{bank_object.name}"); bank_object = Bank.find(:last) puts ("Id is #{bank_object.id}"); puts ("Name is #{bank_object.name}"); bank_object = Bank.find(2) puts ("Id is #{bank_object.id}"); puts ("Name is #{bank_object.name}"); all_banks = Bank.find_by_sql("select * from banks"); for bank in all_banks puts ("#{bank.name}"); end sbi_bank = Bank.find_by_name("SBI Bank"); puts ("Name is #{sbi_bank.name}"); icici_bank = Bank.find_by_head_office("Delhi"); puts ("Name is #{icici_bank.name}"); Bank.find(:all).each do |bank| puts "#{bank.name}" end
The constants ‘:first’ and ‘:last’ carry special meaning in the context of Active Record and when used willl fetch the first and the last records from the database when used in tandem with the find() method. It is also possible to retrieve the data using the primary key value. We have used find() by passing in a value of 2, in this case a comparison will happen between the primary key column with 2. Other variations for finding the objects are through queries and dynamic methods. For example, in the above code, we have used the query for fetching all the bank objects from the database. Another using strategy for retrieving data from the database is to use find_by_<property_name> notation. This means, it is possible to use the methods find_by_name(), find_by_head_office(), find_by_operation_date() directly on the bank obejcts by passing in the appropriate property value.
Edit Records
Having seen the usage of Create and Find, we will see how to use Active Record for editing persistent objects, the following code snippet will illustrate the usage.
require "logger" require "rubygems" require "active_record" require "pp" ActiveRecord::Base.logger = Logger.new(STDOUT) ActiveRecord::Base.establish_connection(:adapter => "mysql" , :database => "ruby", :username => "root", :password => "XXX") class Bank < ActiveRecord::Base end bank = Bank.find(1); bank.name = "HDFC"; bank.save; Bank.update_all("head_office = 'New Location'"); Bank.delete_all();
We have used a flavour of find() method to retrieve the object from the database, and then have updated its properties using the regular approach. A call to save() will now update the corresponding entity in the database, instead of creating a new entity. Likewise, there are other useful methods such as update_all() and delete_all(). A call to update_all() which accept an expression, in the example case we have used ‘head_office = New Location’, this will make all the head_office column values to have the value ‘New Location’. Similarly delete_all() will remove all the records from the database.
Establishing relationship
Complex relationship between objects as well as its corresponding mapping at the database level can be easily achieved using Active Record. The following example illustrates the usage of relationship between the objects blogs and posts. A blog can have multiple posts and each post must know to which blog it belongs to.
require "logger" require "rubygems" require "activerecord" ActiveRecord::Base.logger = Logger.new(STDOUT) ActiveRecord::Base.establish_connection(:adapter => "mysql" , :database => "ruby", :username => "root", :password => "XXX") ActiveRecord::Schema.define do create_table :blogs, :force => true do |b| b.string :name b.string :title b.string :description end create_table :posts, :force => true do |p| p.integer :blog_id p.text :description end end class Blog < ActiveRecord::Base has_many :posts end class Post < ActiveRecord::Base belongs_to :blog end Post.delete_all(); Blog.delete_all(); java_blog = Blog.create( :name => "Java Blog", :title => "Java beat Blog", :description => "Contains Java Articles and Tips" ) post1 = Post.new(); post1.id = 'P1' post1.description = 'Java Articles are great'; post1.blog = java_blog; post1.save; post2 = Post.new(); post2.id = 'P2' post2.description = 'Java Tips are very useful'; post2.blog = java_blog; post2.save; cpp_blog = Blog.create( :name => "CPP Blog", :title => "CPP Blog", :description => "Contains CPP Articles and Tips" ) post3 = Post.new(); post3.id = 'P3' post3.description = 'CPP Articles are great'; post3.blog = cpp_blog; post3.save; post4 = Post.new(); post4.id = 'P4' post4.description = 'CPP Tips are very useful'; post4.blog = cpp_blog; post4.save;
The above example also illustrates the usage of Active Record’ Schema for creating tables and relationships directly from the application. The keywords ‘has_many’ defines a one to many relationship between Blog and Post objects and ‘belong_to’ ensures that the master reference Blog is preserved in the Post object. After running the application, the table structure will look something similar to the following,
Blog Id Name Title Description 1 Java Blog Java beat Blog 2 CPP Blog CPP Blog Post Id Blog Id Description 1 1 Java Articles are great 2 1 Java Tips are very useful 3 2 CPP Articles are great 4 2 CPP Tips are very useful
Creating database aware application
In this example, we will create a sample application for task management. Basically the application will provide options for creating, deleting, editing and viewing task items. We will be taking the support of various utilities that comes as part of Rails distribution for creating this sample.
Creating the project
Create a project called ‘task’ by executing the following command. The following command ensures that the basic set of artifacts necessary for a rails project is created.
rails new task
Also delete the default index.html file which is available in ‘/public/’ directory. Change the current directory to ‘task’ before proceeding with the rest of the section.
Configuring Database
The database that we will be using in this sample application will be mysql. Make sure that mysql database is installed in the local machine. Database related configurations for a rails application goes in database.yml file which is present in ‘/config/’directory. Rails framework provides database configuration options for development, testing and production environments. Edit the database.yml and make sure that the contents of the file matches the following,
development: adapter: mysql database: javabeat_dev username: root password: #### pool: 5 timeout: 5000 test: adapter: mysql database: javabeat_test username: root password: #### pool: 5 timeout: 5000 production: adapter: mysql database: javabeat_prod username: root password: #### pool: 5 timeout: 5000
In the above file, we have configured different databases for different environments. Make sure to modify the username and the password for the database. Also there is one more place to instruct the Rails framework that we want mysql gem for interacting with the mysql database and this happens to be the file ‘Gemfile’ that is present in the project’s root directory.
gem 'mysql', '2.8.1'
Make sure to add the above line in the ‘Gemfile’ and remove any dependencies related to ‘sqlite’ which by default will be present.
The next step is to create the database that we have configured in the database.yml. One way is to connect to the MySql server through a MySql client and then issue ‘create database <DATABASE_NAME>’ for creating the database for the development and the testing environments. Other possible option is to execute to the following command which does the job of creating the database.
rake db:create
Scaffolding
In Rails terms, scaffolding refers to the process of the generation of artifacts such as controllers, models and view. In our case, we want to create controller, model and multiple views for the task item. Execute the following command,
rails generate scaffold TaskItem tile:string summary:text start_date:date end_date:date status:string priority:string total_hours_of_work:integer
We want scaffolding for ‘TaskItem’ where the model ‘Task’ is supposed to contain various properties like title, summary, start_date, end_date, status, priority and total_hours_of_work. Also note that syntax for specifying these list of properties in the command line which follows ‘<propertyName>:<propertyType>’. Property types with values ‘string’ will be displayed as a text field, those with values ‘text’ will be displayed as a text-box, for ‘date’ property types a date selector UI will be provided for selecting month, day and year. Executing the above command will generate a lot of files and folders for the project. In the subsequent sections, we will discuss the details about them.
Model
The model file ‘task_item.rb’ will be generated and placed in the directory ‘/app/models’. The name of the model class will be TaskItem (the one that is specified in the command prompt). The listing for this class is shown below.
class TaskItem < ActiveRecord::Base end
As you can see the TaskItem extends from the base class ‘ActiveRecord:Base’. This inheritance will make sure that the ‘TaskItem’ is a data-aware model class and the capabilities for a data-aware object such as Create/Update/Delete/Read will be applicable to objects created from TaskItem.
Controller
The Controller class provides the actions for creating, editing displaying, deleting and viewing. The following provides code snippet for TaskItemsController.
class TaskItemsController < ApplicationController … end
The controller class must extend the base ‘ApplicationController’ in a data-aware environment. We will see the list of supported actions provided by the controller in the following section.
New Task
Given below is the code snippet for creating a new task action. One can see that a new task is created through ‘TaskItem.new’ and the result is stored in task_item variable. When it comes to the rendering part, in case of html rendering, when the url is accessed through ‘http://localhost:3000/task_items/new’, the view is taken from the file ‘new.html.erb’ which is present in ‘/app/views/task_items’ directory. For xml rendering, when the url is accessed as ‘http://localhost:3000/task_items/new.xml’, the xml format of the new task-item is shown, although one would rarely use this feature.
def new @task_item = TaskItem.new respond_to do |format| format.html # new.html.erb format.xml { render :xml => @task_item } end end
One can see that this above action creates a empty task and redirects the user to a different page where the user can provide information for creating a task. The next section discusses about creating the new task.
Create Task
This action will be triggered once the user presses the ‘Create’ button after giving all the information .
def create @task_item = TaskItem.new(params[:task_item]) respond_to do |format| if @task_item.save format.html { redirect_to(@task_item, :notice => 'Task item was successfully created.') } format.xml { render :xml => @task_item, :status => :created, :location => @task_item } else format.html { render :action => "new" } format.xml { render :xml => @task_item.errors, :status => :unprocessable_entity } end end end
Note that the information collected from the user will be stored in the ‘params’ variable and the list of values present in the variable will be used for creating a new task. Upon successful creation of the task, appropriate message is displayed to the user.
Edit Task
In the case of editing a task, the task item to be edited is fetched from the database by calling the method TaskItem.find() that takes the id of the task as a parameter. Note that the request parameters will always be available in the in-built ‘params’ variable.
def update @task_item = TaskItem.find(params[:id]) respond_to do |format| if @task_item.update_attributes(params[:task_item]) format.html { redirect_to(@task_item, :notice => 'Task item was successfully updated.') } format.xml { head :ok } else format.html { render :action => "edit" } format.xml { render :xml => @task_item.errors, :status => :unprocessable_entity } end end end
We take the id of the task item to be edited and then call the method update_attributes() for updating the task object.
Show Task
The show action resembles the same functionality as that of update functionality. The task item to be shown in the UI is fetched with the help of task item. The fetched task item is then rendered in the browser either in html or in XML depending on the request URL.
def show @task_item = TaskItem.find(params[:id]) respond_to do |format| format.html # show.html.erb format.xml { render :xml => @task_item } end end
Delete Task
For deleting a task also, we follow similar logic as ‘edit’, where the id of the task is taken into consideration for fetching the task to be deleted. The method destroy() can be used for actually deleting the task object.
def destroy @task_item = TaskItem.find(params[:id]) @task_item.destroy respond_to do |format| format.html { redirect_to(task_items_url) } format.xml { head :ok } end end
View all Tasks
The code snippet for viewing all the tasks is given below.
def index @task_items = TaskItem.all respond_to do |format| format.html # index.html.erb format.xml { render :xml => @task_items } end end
The call ‘TaskItem.all’ will retrieve all the task items from the database and the results are stored in the variable task_items. In this action also, html rendering (‘http://localhost:3000/task_items’)as well as xml rendering (‘http://localhost:3000/taskitems.xml’)is supported.
Views
In this section we will look into the various views that the above action definitions are having references.
Index View
The complete listing for the Index view is given below. This view will be displayed by default when the user accesses the URL ‘http://localhost:3000/task_items’.
<h1>Listing all the task items</h1> <table border = '1'> <tr> <th>Tile</th> <th>Summary</th> <th>Start date</th> <th>End date</th> <th>Status</th> <th>Priority</th> <th>Total hours of work</th> <th></th> <th></th> <th></th> </tr> <% @task_items.each do |task_item| %> <tr> <td><%= task_item.tile %></td> <td><%= task_item.summary %></td> <td><%= task_item.start_date %></td> <td><%= task_item.end_date %></td> <td><%= task_item.status %></td> <td><%= task_item.priority %></td> <td><%= task_item.total_hours_of_work %></td> <td><%= link_to 'Show', task_item %></td> <td><%= link_to 'Edit', edit_task_item_path(task_item) %></td> <td><%= link_to 'Destroy', task_item, :confirm => 'Are you sure?', :method => :delete %></td> </tr> <% end %> </table> <br /> <%= link_to 'New Task item', new_task_item_path %>
Note that the above file uses a mixture of html and ruby scriptlets. The very first time the view is accessed it will be empty. Upon creating of sample tasks, the view may look something similar to the following
Listing all the tasks
New Task View
The screen-shot for creating a new task view is shown below.
For creating a new task
The code listing for ‘new.html.erb’ is given as follows.
<h1>Create new task page</h1> <%= render 'form' %> <%= link_to 'Back', task_items_path %>
Here we are using the concept of Partials in Ruby. Paritals are templates for a view that can be reused in multiple places. Here we have defined a partial in the name ‘form’ and hence the convention is that the view file ‘_form.html.erb’ will be placed in the directory ‘/app/views/task_items’ directory. The source code for ‘_form.html.erb’ will be presented later in the section. Given such a template file, it is possible to use the same template for new task as well as for edit task action because the UI controls for ‘new task’ and ‘edit task’ are one and the same.
Edit Task View
The edit task view is not different from ‘new task view’ and it also uses the partial ‘form’, the complete source code of which is given below.
<h1>Edit the selected task</h1> <%= render 'form' %> <%= link_to 'Show', @task_item %> | <%= link_to 'Back', task_items_path %>
The sample screen-shot of ‘edit task’ is give below.
For editing existing task
Show Task View
When the user clicks the Show link from the ‘View all tasks’ page, information about the task will be displayed. Please refer the following screenshot for one such task information.
For viewing a task information
Given below is the source code listing for ‘show task view’. Note that there is view ensures that all the properties of a task are taken into consideration for display.
<h2> View information about the selected task </h2> <b>Tile:</b> <%= @task_item.tile %> <b>Summary:</b> <%= @task_item.summary %> <b>Start date:</b> <%= @task_item.start_date %> <b>End date:</b> <%= @task_item.end_date %> <b>Status:</b> <%= @task_item.status %> <b>Priority:</b> <%= @task_item.priority %> <b>Total hours of work:</b> <%= @task_item.total_hours_of_work %> <%= link_to 'Edit', edit_task_item_path(@task_item) %> | <%= link_to 'Back', task_items_path %>
Conclusion
This article explored the power of Ruby with respect to database integration, especially with Active Record features. The first section of the article discussed on writing basic CRUD operations along with the mapping between Ruby classes and database tables. Establishing relationships are explained with simple example. The later section of the article guided in writing a web application that makes use of Active Record features for creating/editing/listing tasks.