JavaBeat

  • Home
  • Java
    • Java 7
    • Java 8
    • Java EE
    • Servlets
  • Spring Framework
    • Spring Tutorials
    • Spring 4 Tutorials
    • Spring Boot
  • JSF Tutorials
  • Most Popular
    • Binary Search Tree Traversal
    • Spring Batch Tutorial
    • AngularJS + Spring MVC
    • Spring Data JPA Tutorial
    • Packaging and Deploying Node.js
  • About Us
    • Join Us (JBC)
  • Privacy
  • Contact Us

How to write comments controller using Rails?

April 15, 2011 by Krishna Srinivasan Leave a Comment

This article is based on Rails 3 in Action, to be published Fall 2011. It is being reproduced here by permission from Manning Publications. Manning publishes MEAP (Manning Early Access Program,) eBooks and pBooks. MEAPs are sold exclusively through Manning.com. All pBook purchases include free PDF, mobi and epub. When mobile formats become available all customers will be contacted and upgraded. Visit Manning.com for more information. [ Use promotional code ‘java40beat’ and get 40% discount on eBooks and pBooks ]

The Comments Controller

Introduction

In a ticket-tracking application, tickets aren’t just there to provide information of a particular problem or suggestion; rather, they’re there to provide the workflow for it. The general workflow of a ticket is that a user will file it and it’ll be classified as a “new” ticket. When the developers of the project look at this ticket and decide to work on it, they’ll switch the state on the ticket to “open” and once they’re done mark it as “resolved”. If a ticket needs more information on it then another state such as “needs more info”. A ticket could also be a duplicate of another ticket or it could be something that the developers determine isn’t worthwhile putting in. In cases such as this the ticket may be marked as “duplicate” or “invalid,” respectively.

Explanation

The point here is: tickets have a workflow, and that workflow revolves around state changes. We’ll allow the admin users of this application to add states, but not to delete them. The reason for this is if an admin were to delete a state that was used then we’d have no record of that state ever existing. It’s best if once states are created and used on a ticket that they can’t be deleted.

To track the states, we’d let users leave a comment. With a comment, users will be able to leave a text message about the ticket and may also elect to change the state of the ticket to something else by selecting it from a drop down box. However, not all users will be able to leave a comment and change the state. We will protect both creating a comment and changing the state. By the time we’re done with all of this, the users of our application will have the ability to add comments to our tickets.

In order for a comment form to have somewhere to post we need to generate the CommentsController. We can do this by running this command:
[code]rails g controller comments[/code]
A create action in this controller will provide the receiving end for the comment form, so we should add this now. We’ll need to define two before_filters in this controller. The first is to ensure the user is signed in because we don’t want anonymous users creating comments for and another to find the Ticket object. This entire controller is shown in listing 1.

Listing 1 app/controllers/comments_controller.rb
[code]class CommentsController < ApplicationController
before_filter :authenticate_user!
before_filter :find_ticket
def create
@comment = @ticket.comments.build(params[:comment].merge(:user => current_user))
if @comment.save
flash[:notice] = "Comment has been created."
redirect_to [@ticket.project, @ticket]
else
flash[:alert] = "Comment has not been created."
render :template => "tickets/show"
end
end
private
def find_ticket
@ticket = Ticket.find(params[:ticket_id])
end
end

#A redirect_to with array
#B render :template[/code]
In this action we use the template option of render when our @comment.save returns false to render a template of another controller.

You would use the action option to render templates for the current controller. By doing this, the @ticket and @comment objects become available when the app/views/tickets/show.html.erb template is rendered.

If the object saves successfully, we redirect back to the ticket’s page by passing an Array argument to redirect_to, which redirects to a nested route similar to /projects/1/tickets/2.

By creating the controller, we’ve now got all the important parts needed to create comments. Let’s check this feature by running bundle exec cucumber features/creating_comments.feature. We’ll see that it’s able to create the comment but it’s unable to find the text within the #comments element on the page.

Then I should see “Added a comment!” within “#comments” scope ‘//*[@id = ‘comments’]’ not found on page (Capybara::ElementNotFound)

This is because we haven’t added the comments listing to the show template yet. Let’s do this by adding the code from listing 2 above the comment form.

Listing 2 app/views/tickets/show.html.erb
[code]<h3>Comments</h3>
<div id=‘comments’>
<% if @ticket.comments.exists? %> <co id=‘ch09_191_1’ />
<%= render @ticket.comments.select(&:persisted?) %>
<% else %>
There are no comments for this ticket.
<% end %>
</div>[/code]
Here, we create the element our scenario requires: one with an id attribute of comments. In this we check if there are no comments by using the exists?. This will do a very light query similar to this to check if there are any comments:

SELECT “comments”.”id” FROM “comments” WHERE (“comments”.ticket_id = 1) LIMIT 1
It only selects the “id” column from the comments table and limits the result set to 1. We could use empty? here instead, but that would load the comments association in its entirety and then check to see if the array is empty. By using exists?, we stop this potential performance issue from cropping up.

Inside this div, if there are comments, we call render and pass it the argument of @ticket.comments and on the end of that call select on it.

We use select here because we don’t want to render the comment object we’re building for the form at the bottom of the page. If we left off the select, @ticket.comments would include this new object and render a blank comment box. When we call select on an array, we can pass it a block which it will evaluate on all objects inside that array and return any element which makes the block evaluate to anything that is not nil or false.

The argument we pass to select is called a Symbol-to-Proc and is a shorter way of writing this:
{ |x| x.persisted? }

This is a new syntax versions of Ruby = 1.8.7 and used to be in Active Support in Rails 2. It’s a handy way of writing a short block.

The persisted? method checks if an object is persisted in the database by checking if it has its id attribute set and will return true if that’s the case and false if not.

By using render in this form, Rails will render a partial for every single element in this collection and will try to locate the partial using the first object’s class name. Objects in this particular collection are of the Comment, so the partial Rails will try to find will be at app/views/comments/_comment.html.erb, but we don’t have this file right now. Let’s create it and fill it with the content from listing 3.

Listing 3 app/views/comments/_comment.html.erb
[code]<%= div_for(comment) do %>
<h4><%= comment.user %></h4>
<%= simple_format(comment.text) %>
<% end %>[/code]
Here we’ve used a new method, div_for. This method generates a div tag around the content in the block and also sets a class and id attribute based on the object passed in. In this instance, the div tag would be this:
[code]
<div id="comment_1" class="comment">[/code]
The class method from this tag is used to style our comments so that they will look like figure 1 when the styles from the stylesheet are applied.

Figure 1 A commentWith this partial now complete, when we run our feature again by running bundle exec cucumber features/creating_comments.feature, it will all be passing!
[code]2 scenario (2 passed)
23 steps (23 passed)[/code]
Good to see. We have now got the base for users to be able to change the state of a ticket. Before we proceed further, we should make sure that everything is working as it should by running rake cucumber:ok spec and we should also commit our changes. When we run the tests we’ll see this output:
[code]47 scenarios (47 passed)
534 steps (534 passed)
# and
23 examples, 0 failures, 7 pending[/code]
Good stuff! Let’s only commit this now.
[code]git add .
git commit -m "Users can now leave comments on tickets"[/code]
Hey, we’ve got a new file here that begins with capybara in our commit output! This file is the file that was generated by Capybara to show us the page when we used the “Then show me the page” step. We should open our .gitignore file and now ignore this file by putting this line in the file:
[code]capybara*[/code]
This will ignore all capybara files. Let’s remove all the capybara files now and amend this commit and push our changes now.
[code]git rm capybara*
git commit –amend -m "Users can now leave comments on tickets"
git push[/code]
This newly amended commit will override our previous commit, and the push puts our code on GitHub, where it’s safe. With this push, we’ve completed the first half of our state select feature. We now have added the ability for users of our application to add comments.

Summary

We’ve enabled users to leave comments on tickets. This feature is useful because it provides a way for users of a project to have a discussion about a ticket and keep track of it.

Filed Under: Ruby Tagged With: Rails

Creating Tags using Rails

April 15, 2011 by Krishna Srinivasan Leave a Comment

This article is based on Rails 3 in Action, to be published Fall 2011. It is being reproduced here by permission from Manning Publications. Manning publishes MEAP (Manning Early Access Program,) eBooks and pBooks. MEAPs are sold exclusively through Manning.com. All pBook purchases include free PDF, mobi and epub. When mobile formats become available all customers will be contacted and upgraded. Visit Manning.com for more information. [ Use promotional code ‘java40beat’ and get 40% discount on eBooks and pBooks ]

Creating Tags

Introduction

Tags in a ticket-tracking application are extremely useful for making similar tickets easier to find and manage. In this article, we will create the interface for adding tags to a new ticket. This involves adding a new field to the new ticket page and defining a has_and_belongs_to_many association between the Ticket model and the not-yet-existent Tag model.

Creating tags feature

For this feature, we’re going to add a text field beneath the description field on the new ticket page, just like we see in figure 1.

Figure 1 The tag boxThe words we enter into this field will become the tags for this ticket and we should see them on the ticket page. At the bottom of features/creating_tickets.feature, we’ll add a scenario that creates a new ticket with tags, shown in listing 1.

Listing 1 features/creating_tickets.feature

[code]Scenario: Creating a ticket with tags
When I fill in "Title" with "Non-standards compliance"
And I fill in "Description" with "My pages are ugly!"
And I fill in "Tags" with "browser visual"
And I press "Create Ticket"
Then I should see "Ticket has been created."
And I should see "browser" in "#ticket #tags"
And I should see "visual" in "#ticket #tags"[/code]

When we run this scenario using bundle exec cucumber, it will fail features/creating_tickets.feature:48 declaring that it can’t find the “Tags” field. Good! It’s not there yet.

[code]And I fill in "Tags" with "browser visual"
cannot fill in, no text field, text area or password field
with id, name, or label ‘Tags’ found (Capybara::ElementNotFound)[/code]

We’re going to need to take the data from this field and process each word into a new Tag object and, for that reason, we’ll use a text_field_tag to render this field. text_field_tag is similar to a text_field tag, but it doesn’t have to relate to any specific object like text_field does; instead it will just output an input tag with the type attribute set to “text” and the name set to whatever name we give it.

Using text_field_tag

To define this field, we will put the following code underneath the p tag for the description in app/views/tickets/_form.html.erb.

[code]<%= f.label_tag :tags %>
<%= f.text_field_tag :tags, params[:tags] %>[/code]

This field will be sent through to TicketsController as simply params[:tags]. By specifying params[:tags] as the second argument to this method, we can repopulate this field when the ticket cannot be created due to it failing validation.

When we re run this scenario, it no longer complains about the missing “Tags” field, but now that it can’t find the tags area for our ticket:

[code]And I should see "browser" within "#ticket #tags"
scope ‘//*[@id = ‘ticket’]//*[@id = ‘tags’]’ not found …[/code]

We will need to define a #tags element inside the #ticket element so that this part of the scenario will pass. This element will contain the tags for our ticket, which our scenario will assert as actually visible.

Showing tags

We can add this new element to app/views/tickets/show.html.erb by adding this simple line underneath where we render the ticket’s description:

[code]<div id=’tags’><%= render @ticket.tags %></div>[/code]

This creates the #ticket #tags element our feature is looking for and will render the soon-to-be-created app/views/tags/_tag.html.erb partial for every element in the also-soon-to-be-created tags association on the @ticket object.

So what out of these two steps is our next one? We can run our scenario again to see that it cannot find the tags method for a Ticket object:

[code]undefined method ‘tags’ for #<Ticket:0x0..[/code]

This method is the tags method, which we’ll be defining with a has_and_belongs_to_many association between Ticket objects and Tag objects. It will be responsible for returning a collection of all the tags associated with the given ticket, much like a has_many would. It works in the opposite direction also: allowing us to find out what tickets have a specific tag.

Defining the tags association

We can define the association has_and_belongs_to_many on the Ticket model by using this line, placed after a new line after the has_many definitions inside our Ticket model:

[code]has_and_belongs_to_many :tags[/code]

This association will rely on a join table that doesn’t yet exist, called tags_tickets. This table contains only two fields, which are both foreign keys for tags and tickets. By using a join table, many tickets can have many tags, and vice versa.

When we rerun our scenario we’re told that there’s no constant called Tag yet:

[code]
uninitialized constant Ticket::Tag (ActionView::Template::Error)[/code]

In other words, there is no Tag model yet. We should define this now if we want to go any further.

The Tag model

Our Tag model will have a single field called name which should be unique. To generate this model and its related migration we will run the rails command like this:

[code] rails g model tag name:string –timestamps false [/code]

The timestamps option passed here determines whether or not the model’s migration is generated with timestamps. Because we’ve passed the value of false to this, there will be no timestamps added.

Before we run this migration, we will need to add the join table called tags_tickets to our database, which has two fields: one called ticket_id and the other tag_id. The table name is the pluralized names of the two models it is joining, sorted in alphabetical order. This table will have no primary key as we only need it to join the tags and tickets table. We are never going to look for individual records from this table.

To define the table we will put this tags_tickets in the self.up section of our db/migrate/[timestamp]_create_tags.rb migration:

[code]create_table :tags_tickets, :id => false do | t |
t.integer :tag_id, :ticket_id
end[/code]

The :id => false option passed to create_table here tells Active Record to create the table without the id field.

We should also add drop_table :tag_tickets to the self.down method in this migration too so that, if we need to, we can undo this migration. Next, we will run the migration on our development database by running rake db:migrate and our test database by running rake db:test:prepare. This will create the tags and tags_tickets tables. When we run this scenario again with bundle exec cucumber features/creating_tickets:48, it is now satisfied that the tags method is defined and has now moved on to whining that it can’t find the tag we specified:

[code]And I should see "browser" within "#ticket #tags"
Failed assertion, no message given. (MiniTest::Assertion)[/code]

This is because we’re not doing anything to associate the text from the “Tags” field to the ticket we’ve just created. We need to parse the content from this field into new Tag objects and then associate them with the ticket we are creating, which we’ll look at how to do right now.

Displaying a ticket’s tags

The params[:tags] in TicketsController’s create is the value from our “Tags” field on app/views/tickets/_form.html.erb. This is also the field we need to parse into Tag objects and associate them with the Ticket object we are creating.

To do this, we will alter the create action by adding this line directly after @ticket.save:

[code]@ticket.tag!(params[:tags])[/code]

This method will parse the tags from params[:tags], convert them into new Tag objects, and associate them with the ticket. We can define this new method at the bottom of our Ticket model like this:

[code]def tag!(tags)
tags = tags.split(" ").map do |tag|
Tag.find_or_create_by_name(tag)
end
self.tags << tags
end[/code]

On the first line here, we use the split method to split our string into an array, and then the map method to iterate through every value in the array. Inside the block for map, we use a dynamic finder to find or create a tag with a specified name. find_or_create_by methods will always return a record, whether it be a preexisting or a recently created one.

After all the tags have been iterated through, we assign them to a ticket by using the << method on the tags association. The tag! method we have just written will create the tags that we will display on the app/views/tickets/show.html.erb view by using this line:

[code]<%= render @ticket.tags %>[/code]

When we run this scenario again by running bundle exec cucumber features/creating_tickets.feature:48, we will see it’s this line that is failing with an error:
Missing partial tags/tag …

Therefore, the next step is to write the tag partial, which our feature has just complained about. To do this, we will put the following code in app/views/tags/_tag.html.erb:

[code]<span class=’tag’><%= tag.name %></span>[/code]

By wrapping the tag name in a span with the of tag, class, it will be styled as defined in our stylesheet. With this partial defined, this puts the final piece of the puzzle for this feature into place. When we run our scenario again by running bundleexeccucumber features/creating_tickets.feature:48 it passes:

[code]1 scenario (1 passed)
15 steps (15 passed)[/code]

Great! This scenario is now complete. When a user creates a ticket, they are able to assign tags to that ticket and they display along with the ticket’s information on the show action for TicketsController.

Figure 1 Look ma, a tag!We will now commit this change, but, of course, before we do, we’ll ensure that we haven’t broken anything by running rake cucumber:ok spec.

[code]53 scenarios (53 passed)
620 steps (620 passed)
# and
27 examples, 0 failures, 10 pending[/code]

Good to see that nothing’s blown up this time. Let’s commit this change.

[code]git add .
git commit -m "Users can tag tickets upon creation"
git push[/code]

Now that users are able to add a tag to a ticket when that ticket’s being created, we should also let them add tags to a ticket when they create a comment as well.

When a ticket’s discussion happens, new information may come about that would require that another tag be added to the ticket to group it into a different set. A perfect way to let our users do this would be to let them add it when they comment.

Summary

We’ve covered how to use a has_and_belongs_to_many association to define a link between tickets and tags. Tickets are capable of having more than one tag, but a tag is also capable of having more than one ticket assigned to it and, therefore, we use this type of association. A has_and_belongs_to_many could be used to associate people and the locations they’ve been.

Filed Under: Ruby Tagged With: Rails, Ruby

Creating Database aware applications in Ruby on Rails

December 6, 2010 by Krishna Srinivasan Leave a Comment

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.

[code]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");[/code]
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.

[code]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[/code]
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.

[code]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();[/code]
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.

[code]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;[/code]
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.

[code]rails new task[/code]
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,

[code]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[/code]
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.

[code]gem ‘mysql’, ‘2.8.1’[/code]
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.

[code]rake db:create[/code]

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,

[code]rails generate scaffold TaskItem tile:string summary:text start_date:date end_date:date status:string priority:string total_hours_of_work:integer[/code]
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.

[code]class TaskItem < ActiveRecord::Base end[/code]
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.

[code lang=”java”]class TaskItemsController < ApplicationController

…

end[/code]
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.

[code lang=”html”]def new
@task_item = TaskItem.new

respond_to do |format|
format.html # new.html.erb
format.xml { render :xml => @task_item }
end
end[/code]
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 .

[code]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[/code]
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.

[code]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[/code]
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.

[code]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[/code]

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.

[code]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[/code]

View all Tasks

The code snippet for viewing all the tasks is given below.

[code]def index
@task_items = TaskItem.all

respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => @task_items }
end
end[/code]
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’.

[code lang=”html”]<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 %>[/code]
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.

[code lang=”html”]<h1>Create new task page</h1>

<%= render ‘form’ %>

<%= link_to ‘Back’, task_items_path %>[/code]
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.

[code lang=”html”]<h1>Edit the selected task</h1>

<%= render ‘form’ %>

<%= link_to ‘Show’, @task_item %> |
<%= link_to ‘Back’, task_items_path %>[/code]
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.

[code lang=”html”]<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 %>[/code]

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.

Filed Under: Ruby Tagged With: Ruby on Rails

Cloning Internet Applications with Ruby

October 28, 2010 by Krishna Srinivasan Leave a Comment

Cloning Internet Applications with Ruby

We stand on the shoulders of giants. This has been true since the time of Newton (and even before) and it is certainly true now. Much of what we know and learn of programming, we learnt from the pioneering programmers before us and what we leave behind to future generations of programmers is our hard-earned experience and precious knowledge. This book is all about being the scaffolding upon which the next generation of programmers stands when they build the next Sistine Chapel of software.
There are many ways that we can build this scaffolding but one of the best ways is simply to copy from what works. Many programming books attempt to teach with code samples that the readers can reproduce and try it out themselves. This book goes beyond code samples. The reader doesn’t only copy snippets of code or build simple applications but have a chance to take a peek at how a few of the most popular Internet applications today can possibly be built. We explore how these applications are coded and also the rationale behind the way they are designed. The aim is to guide the programmer through the steps of building clones of the various popular Internet applications.

What This Book Covers

Chapter 1, Cloning Internet Applications gives a brief description of the purpose of the book, the target readers of the book, and a list of the four popular Internet applications we will be cloning in the subsequent chapters. The bulk of this chapter gives a brief rundown
on the various technologies we will be using to build those clones.
Chapter 2, URL Shorteners – Cloning TinyURL explains about the first popular Internet application that we investigate and clone in the book, which is TinyURL. This chapter describes how to create a TinyURL clone, its basic principles, and algorithms used
Chapter 3, Microblogs – Cloning Twitter. The clone in this chapter emulates one of the hottest and most popular Internet web applications now—Twitter. It describes the basic principles of a microblogging application and explains how to recreate a feature-complete
Twitter clone. Chapter 4, Photo -sharing – Cloning Flickr. Flickr is one of the most popular and enduring photo-sharing applications on the Internet. This chapter describes how the reader can re-create a feature complete photo-sharing application the simplest way possible, following the interface and style in Flickr.
Chapter 5, Social Networking Services – Cloning Facebook 1. The final two chapters describe the various aspects of Internet social networking services, focusing on one of the most popular out there now—Facebook. These two chapters also describe the minimal features of a social networking service and show the reader how to implement these features in a complete step-by-step guide. The first part is described in this chapter, which sets the groundwork for the clone and proceeds to describe the data model used in the clone.
Chapter 6, Social Networking Services – Cloning Facebook 2. The final chapter is part two in describing how to create a Facebook clone. This chapter follows on the previous chapter and describes the application flow of the Facebook clone we started earlier.

Social Networking Services – Cloning Facebook 1

One of the most dominant Internet services today is the social networking service. According to a report by the Nielsen Company, in January 2010, the amount of time an average person spent on Facebook is more than seven hours per month, which amounts to more than 14 minutes per day. If you lump together the time spent on Google, Yahoo!, YouTube, Bing, Wikipedia, and Amazon, it still doesn’t beat Facebook! By March 2010, Facebook accounted for more than seven percent of all Internet traffic in the United States, surpassing visits to Google. Social networking services have risen in the past few years to be more than just a passing fad, to be an important communications tool as well as a part of daily life.
We will be building our last and most complex clone based on Facebook, the most popular social networking service as of date. The clone we will build here will be described over this and the next chapter. In this chapter we will cover basic information about social networking services, main features of the clone that we will build, as well as the description of the data model we will be using for the clone.

All about social networking services

A social networking service is an Internet service that models social relationships among people. Essentially it consists of a user profile, his or her social links, and a variety of additional services. Most social networking services are web-based and provide various ways for users to interact over the Internet, including sharing content and communications.
Early social networking websites started in the form of generalized online communities such as The WELL (1985), theglobe.com (1994), GeoCities (1994), and Tripod (1995). These early communities focused on communications through chat rooms, and sharing personal information and topics via simple publishing tools. Other communities took the approach of simply having people link to each other via e-mail addresses. These sites in cluded Classmates (1995), focusing on ties with former schoolmates, and SixDegrees (1997), focusing on indirect ties.
SixDegrees.com in a way was the first to bring together the first few defining features of a social networking service. The basic features of the first online social networking services include user profiles, adding friends to a friends list, and sending private messages. Unfortunately, SixDegrees was too advanced for its time and eventually closed in 2001.
Interestingly the most popular social networking service in Korea, CyWorld, was started around this time in 1999. The original intention for CyWorld was to develop an online dating service similar to Match and provide an open public meeting place for users to meet online. In 2001, CyWorld launched the minihompy service, a feature that allows each user to create a virtual homepage. This was highly successful as celebrities and politicians took to this platform to reach out to their fans and audience. CyWorld also eventually included a virtual currency called “dottori” in 2002 and a mobile version in 2004. Up to 2008, CyWorld had more than one third of Korea’s entire population as members with a strong penetration of ninety percent in the young adults market.
Between 2002 and 2004, a few social networking services became highly popular. Friendster, started by Jon Abraham in 2002 to compete with Match .com , was highly successful initially. However due to platform and scalability issues, its popularity plummeted as newer social networking services were launched. MySpace , launched in 2003, was started as a Friendster alternative and became popular with independent rock bands from Los Angeles as promoters used the platform to advertise VIP passes for popular clubs. Subsequently, MySpac e facilitated a two-way conversation between bands and their fans, and music became the growth engine of MySpace. MySpace also introduced the concept of allowing users to personalize their pages and to generate unique layouts and backgrounds. Eventually MySpace became the most dominant social networking service in U.S. until Facebook took over in 2009.
Mixiis the largest online social networking service in Japan with a total of 20 million users to date and over ninety percent of users being Japanese. Launched in February 2004 by founder KenjiKasahara, the focus of Mixiis to enable users to meet new people who share common interests. An interesting feature of Mixi(counterintuitive) is that it’s an invitation by friend social network, which means that a new user can only join Mixithrough an invitation by an existing user. This feature is only found in niche and private social networks such as http://www.asmallworld.net, a successful social networking service that caters to celebrities and high net worth individuals. This invitation-based model holds the user responsible for who they invite, and thus reduces unwanted behavior within the network, refl ecting Japanese culture itself.
Social networking began to emerge as a part of business Internet strategy at around 2005 when Yahoo! launched Yahoo! 360 , its first attempt at a social networking service. In July 2005 News Corporation bought MySpace. It was around this time as well that the first mainland Chinese social networks started. The three most notable examples in chronological order are 51.com (2005), Xiaonei(2005), and Kaixin001 (2008). 51.com drew its inspiration from CyWorld, and later MySpace and QQ. On the other hand, Xiaoneihas a user interface that follows Facebook, though it also offers the user flexibility to change the look and feel, similar to MySpace. Kaixin001, the latest social networking platform in China with the fastest growing number of users, started in 2008 and the platform and user interface are remarkably similar to Facebook.
It was also around this time that more niche social networking services focusing on specific demographics sprang up, with the most successful example being LinkedIn, which focused on business professionals. At the same time media content sharing sites began slowly incorporated social networking service features and became social networking services themselves. Examples include QQ (instant messaging), Flickr (photo-sharing), YouTube (video-sharing), and Last.FM (music sharing).
As mentioned earlier, as of early 2010 social networking services are the dominant service and purpose for many users on the Internet, with Internet traffic in US surpassing the previous giant of the Internet.

Facebook


Facebook is the most dominant social networking service till date, with 400 million active users, 5 billion pieces of content shared each week, and more than 100 million active users concurrently accessing Facebook through their mobile devices. It is also the most widespread, with 70 percent of its users from outside of US, its home market.
Mark Zuckerberg and some of his Harvard college roommates launched Facebook in February 2004. Initially intended as an online directory for college students (the initial membership was limited to Harvard College students) it was later expanded to include other colleges, then high schools, and finally anyone around the world who is 13 years old and above.
Facebook features are typically that of many social networks that were created around that time. The more prominent ones are the Wall (a space on every user’s profile five friends to post messages on), pokes (which allows users to virtually poke each other, that is to notify a user that they have been poked), photo uploading, sharing, and status updates, which allow users to inform their friends of their whereabouts and what they were doing. Over time, Facebook included features to form virtual groups, to blog, to start events, chat with instant messaging, and even send virtual gifts to friends.
Facebook launched Facebook Platform in May 2007, providing a framework for software developers to create applications that interact with Facebook. It soon became wildly popular, and within a year 400,000 developers have registered for the platform, and built 33,000 applications. As of writing date there are more than 500,000 active applications in Facebook, developed by more than 1 million developers and there are more than 250 applications with more than 1 million monthly active users!
In this chapter we will be cloning Facebook and creating an application called Colony, which has the basic but essential features of Facebook.

Main features

Online social networking services are complex applications with a large number of features. However, these features can be roughly grouped into a few common categories:

  • User
  • Community
  • Content-sharing
  • Developer

User features are features that relate directly to and with the user. For example, the ability to create and share their own profiles, and the ability to share status and activities are user features. Community features are features that connect users with each other. An example of this is the friends list feature, which shows the number of friends a user has connected with in the social network.
Content sharing features are quite easy to understand. These are features that allow a user to share his self-created content with other users, for example photo sharing or blogging. Social bookmarking features are those features that allow users to share content they have discovered with other users, such as sharing links and tagging items with labels. Finally, developer features are features that allow external developers to access the services and data in the social networks.
While the social networking services out in the market often try to differentiate themselves from each other in order to gain an edge over their competition, in this chapter we will be building a stereotypical online social networking service. We will be choosing only a few of the more common features in each category, except for developer features, which for practical reasons will not be implemented here.
Let’s look at these features we will implement in Colony, by category.

User


User features are features that relate directly to users:

  • Users’ activities on the system are broadcast to friends as an activity feed.
  • Users can post brief status updates to all users.
  • Users can add or remove friends by inviting them to link up. Friendship in
    both ways need to be approved by the recipient of the invitation.

Community


Community features connect users with each other:

  • Users can post to a wall belonging to a user, group, or event. A wall is a place where any user can post on and can be viewed by all users.
  • Users can send private messages to other users.
  • Users can create events that represent an actual event in the real world. Events pull together users, content, and provide basic event management capabilities, such as RSVP.
  • Users can form and join groups. Groups represent a grouping of like-minded people and pulls together users and content. Groups are permanent.
  • Users can comment on various types of shared and created content including photos, pages, statuses, and activities. Comments are textual only.
  • Users can indicate that they like most types of shared and created content including photos, pages, statuses, and activities.
  • Content sharing


    Content sharing features allow users to share content, either self-generated or discovered, with other users:

    • Users can create albums and upload photos to them
    • Users can create standalone pages belonging to them or attached pages belonging to events and groups

    You might notice that some of the features in the previous chapters are similar to those here. This should not be surprising. Online social networking services grew from existing communications and community services, often evolving and incorporating features and capabilities from those services. The approach adopted in this book is no different. We will be using some of the features we have built in the previous chapter and adapt them accordingly for Colony.

    For the observant reader you might notice that the previous chapters have clones that end with clone. The original name of this clone during writing was Faceclone, but apparently Facebook has trademarked Face for many of its applications. In order to avoid any potential trademark issues, ichose Colony instead.

    Designing the clone

    Now that we have the list of features that we want to implement for Colony, let’s start designing the clone. The design and implementation of this clone will be described over this and the next chapter. We will start with the data model in this chapter and move on to describing the application flow and deployment with the next chapter.

    Authentication, access control, and user management


    Authentication, access control, and user management are handled much the same as in previous chapters. As with the other clones, authentication is done through RPX, which means we delegate authentication to a third party provider such as Google, Yahoo!, or Facebook. Access control however is still done by Colony, while user management functions are shared between the a uthentication provider and Colony.
    Access control in Colony is done on all data, which prevents user from accessing data that they are not allowed to. This is done through control of the user account, to which all other data for a user belongs. In most cases a user is not allowed access to any data that does not belong to him/her (that is not shared to everyone). In some cases though access is implicit; for example, an event is accessible to be viewed only if you are the organizer of the event. Note that unlike Photoclone, which has public pages, there are no public pages in Colony.
    As before, user management is a shared responsibility between the third party provider and the clone. The provider handles password management and general security while Colony stores a simple set of profile information for the user.

    Status updates


    Allowing you to send status updates about yourself is a major feature of all social networking services. This feature allows the user, a member of the social networking service, to announce and define his presence as well as state of mind to his network. If you have gone through the Twitter clone chapter, you might notice that this feature is almost the same as the one in Tweetclone.
    The major difference in the features is in who can read the statuses, which can be quite subtle yet obvious to someone who has read the previous chapters. In Tweetclone, the user’s followers can read the statuses (which is really just anyone who chooses to follow him or her) while in Colony, only the user’s friends can read the statuses. Remember that a user’s friend is someone validated and approved by the user and not just anyone off the street who happens to follow that user.
    Status updates belong to a single user but are viewable to all friends as a part of the user’s activity feed.

    User activity feeds and news feeds


    Activity feeds, activity streams, or life streams are continuous streams of information on a user’s activities. Activity feeds go beyond just status updates; they are a digital trace of a user’s activity in the social network, which includes his status updates. This include public actions like posting to a wall, uploading photos, and commenting on content, but not private actions like sending messages to individuals. The user’s activity feed is visible to all users who visit his user page.
    Activity feeds are a subset of news feeds that is an aggregate of activity feeds of the user and his network. News feeds give an insight into the user’s activities as well as the activities of his network. In the design of our clone, the user’s activity feed is what you see when you visit the user page, for example http://colony.saush.com/user/sausheong, while the news feed is what you see when you first log in to Colony, that’s the landing page. This design is quite common to many social networking services.

    Friends list and inviting users to join


    One of the reasons why social networking services are so wildly successful is the ability to reach out to old friends or colleagues, and also to see friends of your friends. To clone this feature we provide a standard friends list and an option to search for friends. Searching for friends allows you to find other users in the system by their nicknames or their full names. By viewing a user’s page, we are able to see his friends and therefore see his friend’s user pages as well.
    Another critical feature in social networking services is the ability to invite friends and spread the word around. In Colony we tap on the capabilities of Facebook and invite friends who are already on Facebook to use Colony. While there is a certain amount of irony (using another social networking service to implement a feature of your social networking service), it makes a lot of practical sense, as Facebook is already one of the most popular social networking services on the planet. To implement this, we will use Facebook Connect. However, this means if the user wants to reach out and get others to join him in Colony he will need to log into Facebook to do so.
    As with most features, the implementation can be done in many ways and Facebook Connect (or any other type of third-party integration for that matter) is only one of them. Another popular strategy is to use web mail clients such as Yahoo! Mail or Gmail, and extract user contacts with the permission of the user. The e-mails extracted this way can be used as a mailing list to send to potential users. This is in fact a strategy used by Facebook.

    Posting to the wall


    A wall is a place where users can post messages. Walls are meant to be publicly read by all visitors. In a way it is like a virtual cork bulletin board that users can pin their messages on to be read by anyone. Wall posts are meant to be short public messages. The Messages feature can be used to send private messages.
    A wall can belong to a user, an event, or a group and each of these owning entities can have only one wall. This means any post sent to a user, event, or group is automatically placed on its one and only wall.
    A message on a wall is called a post, which in Colony is just a text message (Facebook’s original implementation was text only but later extended to other types of media). Posts can be remarked on and are not threaded. Posts are placed on the wall in a reverse chronological order in a way that the latest post remains at the top of the wall.

    Sending messages


    The messaging feature of Colony is a private messaging mechanism. Messages are sent by senders and received by recipients. Messages that are received by a user are placed into an inbox while messages that the user sent are placed into a sent box. For Colony we will not be implementing folders so these are the only two message folders that every user has.
    Messages sent to and received from users are threaded and ordered by time. We thread the messages in order to group different messages sent back and forth as part of an ongoing conversation. Threaded messages are sorted in chronological order, where the last received message is at the bottom of the message thread.

    Attending events


    Events can be thought of as locations in time where people can come together for an activity. Social networking services often act as a nexus for a community so organizing and attending events is a natural extension of the features of a social networking service. Events have a wall, venue, date, and time where the event is happening, and can have event-specific pages that allow users to customize and market their event.
    In Colony we categorize users who attend events by their attendance status. Confirmed users are users who have confirmed their attendance. Pending users are users who haven’t yet decided to attend the event. Declined users are users who have declined to attend the event after they have been invited. Declinations are explicit; there is an invisible group of users who are in none of the above three types.
    Attracting users to events or simply keeping them informed is a critical part of making this or any feature successful. To do so, we suggest events to users and display the suggested events in the user’s landing page. The suggestion algorithm is simple, we just go through each of the user’s friends and see which other events they have confirmed attending, and then suggest that event to the user.
    Besides suggestions, the other means of discovering events are through the activity feeds (whenever an event is created, it is logged as an activity and published on the activity feed) and through user pages, where the list of a user’s pages are also displayed. All events are public, as with content created within events like wall posts and pages.

    Forming groups


    Social networking services are made of people and people have a tendency to form groups or categories based on common characteristics or interests. The idea of groups in Colony is to facilitate such grouping of people with a simple set of features. Conceptually groups and events are very similar to each other, except that groups are not time-based like events, and don’t have a concept of attendance. Groups have members, a wall, and can have specific pages created by the group.
    Colony’s capabilities to attract users to groups are slightly weaker than in events. Colony only suggests groups in the groups page rather than the landing page. However, groups also allow discovery through activity feeds and through user pages. Colony has only public groups and no restriction on who can join these public groups.

    Commenting on and liking content


    Two popular and common features in many consumer focused web applications are reviews and ratings. Reviews and ratings allow users to provide reviews (or comments) or ratings to editorial or user-generated content. The stereotypical review and ratings feature is Amazon.com’s book review and rating, which allows users to provide book reviews as well as rate the book from one to five stars.
    Colony’s review feature is called comments. Comments are applicable to all usergenerated content such as status updates, wall posts, photos, and pages. Comments provide a means for users to review the content and give critique or encouragement to the content creator.
    Colony’s rating feature is simple and follows Facebook’s popular rating feature, called likes. While many rating features provide a range of one to five stars for the users to choose, Colony (and Facebook) asks the user to indicate if he likes the content. There is no dislike though, so the fewer number of likes a piece of content, the less popular it is.
    Colony’s comments and liking feature is applicable to all user-generated content such as statuses, photos, wall posts, activities, and pages.

    Sharing photos


    Photos are one of the most popular types of user-generated content shared online, with users uploading 3 billion photos a month on Facebook; it’s an important feature to include in Colony. The photo-sharing feature in Colony is similar to the one in Photoclone.
    The basic concept of photo sharing in Colony is that each user can have one or more albums and each album can have one or more photos. Photos can be commented, liked, and annotated. Unlike in Photoclone, photos in Colony cannot be edited.

    Blogging with pages


    Colony’s pages are a means of allowing users to create their own full-page content, and attach it to their own accounts, a page, or a group. A user, event, or group can own one or more pages. Pages are meant to be user-generated content so the entire content of the page is written by the user. However in order to keep the look and feel consistent throughout the site, the page will be styled according to Colony’s look
    and feel. To do this we only allow users to enter Markdown, a lightweight markup language that takes many cues from existing conventions for marking up plain text in e-mail. Markdown converts its marked-up text input to valid, well-formed XHTML. We use it here in Colony to let users write content easily without worrying about layout or creating a consistent look and feel.

    Technologies and platforms used


    We use a number of technologies in this chapter, mainly revolving around the Ruby programming language and its various libraries. Most of them have been described in Chapter 1. In addition to Ruby and its libraries we also use mashups, which are described next.

    Mashups


    As with previous chapters, while the main features in the applications are all implemented within the chapters itself, sometimes we still depend on other services provided by other providers. In this chapter we use four such external services—RPX for user web authentication, Gravatar for avatar services, Amazon Web Services S3 for photo storage, and Facebook Connect for reaching out to users on Facebook. RPX, Gravatar, and AWS S3 have been explained in previous chapters.

    Facebook Connect


    Facebook has a number of technologies and APIs used to interact and integrate with their platform, and Facebook Connect is one of them. Facebook Connect is a set of APIs that let users bring their identity and information into the application itself. We use Facebook Connect in this chapter to send out requests to a user’s friends, inviting them to join our social network. The steps to integrate with Facebook Connect are detailed in Chapter 6, Social Networking Services — Cloning Facebook 2.
    Note that for the user invitation feature, once a user has logged in through Facebook with RPX, he is considered to have logged into Facebook Connect and therefore can send invitations immediately without logging in again.

    Building the clone

    This is the largest clone built in the book and has many components. Unlike the previous chapters where all the source code are listed in the chapter itself, some of the less interesting parts of the code are not listed or described here. To get access to the full source code please go to http://github.com/sausheong/Colony

    Configuring the clone


    We use a few external APIs in Colony so we need to configure our access to these APIs. In a Colony all these APikeys and settings are stored in a Ruby file called config.rb , shown as below:
    [code]
    S3_CONFIG = {}
    S3_CONFIG[‘AWS_ACCESS_KEY’] = ‘<AWS ACCESS KEY>’
    S3_CONFIG[‘AWS_SECRET_KEY’] = ‘<AWS SECRET KEY>’
    RPX_API_KEY = ‘<RPX APiKEY>’
    [/code]

    Modeling the data


    This is the chapter with the largest number of classes and relationships. A few major classes you see here are similar but not exactly the same as the ones in the previous chapters, so if you have gone through those chapters you would roughly know how it works.
    The following diagram shows how the clone is modeled:

    User


    As before the first class we look at is the User class. If you have followed the previous chapters you will have realized that that this class is very similar to the ones before. However, the main differences are that there are more relationships with other classes and the relationship with other users follows that of a friends model rather than a followers model.
    [code]
    class User
    include DataMapper::Resource
    property :id, Serial
    property :email, String, :length => 255
    property :nickname, String, :length => 255
    property :formatted_name, String, :length => 255
    property :sex, String, :length => 6
    property :relationship_status, String
    property :provider, String, :length => 255
    property :identifier, String, :length => 255
    property :photo_url, String, :length => 255
    property :location, String, :length => 255
    property :description, String, :length => 255
    property :interests, Text
    property :education, Text
    has n, :relationships
    has n, :followers, :through => :relationships, :class_name =>
    ‘User’, :child_key => [:user_id]
    has n, :follows, :through => :relationships, :class_name => ‘User’,
    :remote_name => :user, :child_key => [:follower_id]
    has n, :statuses
    belongs_to :wall
    has n, :group s, :through => Resource
    has n, :sent_messages, :class_name => ‘Message’, :child_key =>
    [:user_id]
    has n, :received_messages, :class_name => ‘Message’, :child_key =>
    [:recipient_id]
    has n, :confirms
    has n, :confirmed_events, :through => :confirms, :class_name =>
    ‘Event’, :child_key => [:user_id], :date.gte => Date.today
    has n, :pendings
    has n, :pending_events, :through => :pendings, :class_name =>
    ‘Event’, :child_key => [:user_id], :date.gte => Date.today
    has n, :requests
    has n, :albums
    has n, :photos, :through => :albums
    has n, :comments
    has n, :activities
    has n, :pages
    validates_is_unique :nickname, :message => "Someone else has taken
    up this nickn ame, try something else!"
    after : create, :create_s3_bucket
    after :create, :create_wall
    def add_friend(user)
    Relationship.create(:user => user, :follo wer => self)
    end
    def friends
    (followers + follows).uniq
    end
    def self.find(identifier)
    u = first(:identifier => identifier)
    u = new(:identifier => identifier) if u.n il?
    return u
    end

    def feed
    feed = [] + activities
    friends.each do |friend|
    feed += friend.activities
    end
    return feed.sort {|x,y| y.created_at <=> x.created_at}
    end

    def possessive_pronoun
    sex.downcase == ‘male’ ? ‘his’ : ‘her’
    end

    def pronoun
    sex.downcase == ‘ male’ ? ‘he’ : ‘she’
    end

    def create_s3_bucket
    S3.create_ bucket("fc.#{id}")
    end

    def create_wall
    self.wall = Wall.create
    self.save
    end

    def all_events
    confirmed_eve nts + pending_events
    end

    def friend_events
    events = []
    friends.each do |friend|
    events += friend.confirmed_events
    end
    return events.sort {|x,y| y.time <=> x.time}
    end
    def friend_groups
    groups = []
    friends.each do |friend|
    groups += friend.groups
    end
    groups – self.groups
    end
    end
    [/code]
    As mentioned in the design section above, the data used in Colony is user-centric. All data in Colony eventually links up to a user. A user has the following relationships with other models:

    • A user has none, one, or more status updates
    • A user is associated with a wall
    • A user belongs to none, one, or more groups
    • A user has none, one, or more sent and received messages
    • A user has none, one, or more confirmed and pending attendances at events
    • A user has none, one, or more user invitations
    • A user has none, one, or more albums and in each album there are none, one, or more photos
    • A user makes none, one, or more comments
    • A user has none, one, or more pages
    • A user has none, one, or more activities
    • Finally of course, a user has one or more friends.

    Once a user is created, there are two actions we need to take. Firstly, we need to create an Amazon S3 bucket for this user, to store his photos.
    [code]
    after :create, :create_s3_bucket

    def create_s3_bucket
    S3.create_bucket("fc.#{id}")
    end
    [/code]
    We also need to create a wall for the user where he or his friends can post to.
    [code]
    after :create, :create_wall
    def create_wall
    self.wall = Wall.create
    self.save
    end
    [/code]
    Adding a friend means creating a relationship between the user and the friend.
    [code]
    def add_friend(user)
    Relationship.create(:user => user, :follower => self)
    end
    [/code]
    You might realize that this was a follows relationship in the previous chapters, so you might ask how could it go both ways? The answer to this question will be clearer once the discussion turns to sending a request to connect. In short, Colony treats both following and follows relationships as going both ways—they are both considered as a friends relationship. The only difference here is who will initiate the request to join. This is why when we ask the User object to give us its friends, it will add both followers and follows together and return a unique array representing all the user’s friends.
    [code]
    def friends
    (followers + follows).uniq
    end
    [/code]
    The Relationship class is almost the same as the one used in the other chapters, except that each time a new relationship is created, an Activity object is also created to indicate that both users are now friends.
    [code]
    class Relationship
    include DataMapper::Resource

    property :user_id, Integer, :key => true
    property :follower_id, Integer, :key => true
    belongs_to :user, :child_key => [:user_id]
    belongs_to :follower, :class_name => ‘User’, :child_key =>
    [:follower_id]
    after :save, :add_activity

    def add_activity
    Activity.create(:user => user, :activity_type => ‘relationship’,
    :text => "<a href=’/user/#{user.nickname}’>#{user.formatted_name}</a>
    and <a href=’/user/#{follower.nickname}’>#{follower.formatted_name}</
    a> are now friends.")
    end
    end
    [/code]
    Finally we get the user’s news feed by taking the user’s activities and going through each of the user’s friends, their activities as well.
    [code]
    def feed
    feed = [] + activities
    friends.each do |friend|
    feed += friend.activities
    end
    return feed.sort {|x,y| y.created_at <=> x.created_at}
    end
    [/code]

    Request


    We use a simple mechanism for users to invite other users to be their friends. The mechanism goes like this:

    1. Alice identifies another Bob whom she wants to befriend and sends him an invitation.
    2. This creates a Request class which is then attached to Bob.
    3. When Bob approves the request to be a friend, Alice is added as a friend (which is essentially making Alice follow Bob, since the definition of a friend in Colony is someone who is either a follower or follows another user).

    [code]
    class Request
    include DataMapper::Resource
    property :id, Serial
    property :text, Text
    property :created_at, DateTime
    belongs_to :from, :class_name => User, :child_key => [ :from_id]
    belongs_to :user
    def approve
    self.user.add_friend(self.from)
    end
    end
    [/code]

    Message


    Messages in Colony are private messages that are sent between users of Colony. As a result, messages sent or received are not tracked as activities in the user’s activity feed.
    [code]
    class Message
    include DataMapper::Resource
    property :id, Serial
    property :subject, String
    property :text, Text
    property :created_at, DateTime
    property :read, Boolean, :default => false
    property :thread, Integer
    belongs_to :sender, :class_name => ‘User’, :child_key => [:user_id]
    belongs_to :recipient, :class_name => ‘User’, :child_key =>
    [:recipient_id]
    end
    [/code]
    A message must have a sender and a recipient, both of which are users.
    [code]
    has n, :sent_messages, :class_name => ‘Message’, :child_key => [:user_
    id]
    has n, :received_messages, :class_name => ‘Message’, :child_key =>
    [:recipient_id]
    [/code]
    The read property tells us if the message has been read by the recipient, while the thread property tells us how to group messages together for display.

    Album


    The photo sharing capabilities of Colony is transplanted from Photoclone in the previous chapter and therefore the various models involved in photo sharing are almost the same as the one in Photoclone. The main difference is that each time an album is created, an activity is logged.
    [code]
    class Album
    include DataMapper::Resource
    property :id, Serial
    property :name, String, :length => 255
    property :description, Text
    property :created_at, DateTime

    belongs_to :user
    has n, :photos
    belongs_to :cover_photo, :class_name => ‘Photo’, :child_key =>
    [:cover_photo_id]
    after :save, :add_activity

    def add_activity
    Activity.create(:user => user, :activity_type => ‘album’, :text =>
    "<a href=’/user/#{user.nickname}’>#{user.formatted_name}</a> created a
    new album <a href=’/album/#{self.id}’>#{self.name}</a>")
    end
    end
    [/code]

    Photo


    The Photo class is the main class in the photo-sharing feature of Colony. Just like the Album class, this is very similar to the one in Photoclone, except for some minor differences.
    [code]
    class Photo
    include DataMapper::Resource
    include Commentable
    attr_writer :tmpfile
    property :id, Serial
    property :title, String, :length => 255
    property :caption, String, :length => 255
    property :privacy, String, :default => ‘public’

    property :format, String
    property :created_at, DateTime

    belongs_to :album

    has n, :annotations
    has n, :comments
    has n, :likes

    after :save, :save_image_s3
    after :create, :add_activity
    after :destroy, :destroy_image_s3

    def filename_display; "#{id}.disp"; end
    def filename_thumbnail; "#{id}.thmb"; end

    def s3_url_thumbnail; S3.get_link(s3_bucket, filename_thumbnail,
    Time.now.to_i+ (24*60*60)); end
    def s3_url_display; S3.get_link(s3_bucket, filename_display, Time.
    now.to_i+ (24*60*60)); end

    def url_thumbnail
    s3_url_thumbnail
    end

    def url_display
    s3_url_display
    end

    def previous_in_album
    photos = album.photos
    index = photos.index self
    return nil unless index
    photos[index – 1] if index > 0
    end

    def next_in_album
    photos = album.photos
    index = photos.index self
    return nil unless index
    photos[index + 1] if index < album.photos.length
    end

    def save_image_s3
    return unless @tmpfile
    img = Magick::Image.from_blob(@tmpfile.open.read).first
    display = img.resize_to_fit(500, 500)
    S3.put(s3_bucket, filename_display, display.to_blob)

    t = img.resize_to_fit(150, 150)
    length = t.rows > t.columns ? t.columns : t.rows
    thumbnail = t.crop(CenterGravity, length, length)
    S3.put(s3_bucket, filename_thumbnail, thumbnail.to_blob)
    end

    def destroy_image_s3
    S3.delete s3_bucket, filename_display
    S3.delete s3_bucket, filename_thumbnail
    end

    def s3_bucket
    "fc.#{album.user.id}"
    end
    def add_activity
    Activity.create(:user => album.user, :activity_type => ‘photo’,
    :text => "<a href=’/user/#{album.user.nickname}’>#{album.user.
    formatted_name}</a> added a new photo – <a href=’/photo/#{self.
    id}’><img class=’span-1′ src=’#{self.url_thumbnail}’/></a>")
    end
    end
    [/code]
    First of all, we removed the feature of storing temporary file caches on the filesystem of the server. The main reason is that of economy—we want to be able to deliver everything from Amazon S3 deploy on the Heroku cloud platform (which does not serve files). Of course this can be changed easily if you’re planning to customize Colony.
    Next, and related to the first difference, is that we no longer store the original photo. Instead, we only keep a reduced-size display photo and a thumbnail of the original photo. The rationale for this is the same as with Facebook. Colony is not a full-fl edged photo-sharing site for photographers and is meant to share photos with friends only. Therefore storing large original files is unnecessary.
    Photos can be commented on so it includes the Commentable module (explained later). Also each photo has none, one, or more comments and likes.
    Finally as with many of the classes in Colony, creating a Photo is considered an activity and is logged for streaming on the activity feed. Note that we don’t log an activity after a save, but only after we create an object photo. This is because save will be called each time the photo object is edited, annotated, or has its caption or description modified. This is not an activity we want to log in to the activity stream.
    [code]
    class Annotation
    include DataMapper::Resource
    property :id, Serial
    property :description,Text
    property :x, Integer
    property :y, Integer
    property :height, Integer
    property :width, Integer
    property :created_at, DateTime

    belongs_to :photo
    after :create, :add_activity

    def add_activity
    Activity.create(:user => self.photo.album.user, :activity_type
    => ‘annotation’, :text => "<a href=’/user/#{self.photo.album.user.
    nickname}’>#{self.photo.album.user.formatted_name}</a> annotated
    a photo – <a href=’/photo/#{self.photo.id}’><img class=’span-1′
    src=’#{self.photo.url_thumbnail}’/></a> with ‘#{self.description}’")
    end
    end
    [/code]
    Annotation is another class, part of the photo-sharing feature that is transplanted from Photoclone, with activity logging added in. We will not go into this, if you want a refresher please read Chapter 4.

    Status


    Just as the Album, Photo, and Annotation classes are transplanted from Photoclone, the Status and Mention classes are derived from Tweetclone.
    [code]
    class Status
    include DataMapper::Resource
    include Commentable

    property :id, Serial
    property :text, String, :length => 160
    property :created_at, DateTime
    belongs_to :recipient, :class_name => "User", :child_key =>
    [:recipient_id]
    belongs_to :user
    has n, :mentions
    has n, :mentioned_users, :through => :mentions, :class_name =>
    ‘User’, :child_key => [:user_id]
    has n, :comments
    has n, :likes

    before :save do
    @mentions = []
    process
    end

    after :save do
    unless @mentions.nil?
    @mentions.each {|m|
    m.status = self
    m.save
    }
    end
    Activity.create(:user => user, :activity_type => ‘status’, :text
    => self.text )
    end

    # general scrubbing
    def process
    # process url
    urls = self.text.scan(URL_REGEXP)
    urls.each { |url|
    tiny_url = RestClient.get "http://tinyurl.com/api-create.
    php?url=#{url[0]}"
    self.text.sub!(url[0], "<a href=’#{tiny_url}’>#{tiny_url}</a>")
    }
    # process @
    ats = self.text.scan(AT_REGEXP)
    ats.each { |at|
    user = User.first(:nickname => at[1,at.length])
    if user
    self.text.sub!(at, "<a href=’/#{user.nickname}’>#{at}</a>")
    @mentions << Mention.new(:user => user, :status => self)
    end
    }
    end

    def starts_with?(prefix)
    prefix = prefix.to_s
    self.text[0, prefix.length] == prefix
    end
    def to_json(*a)
    {‘id’ => id, ‘text’ => text, ‘created_at’ => created_at, ‘user’ =>
    user.nickname}.to_json(*a)
    end
    end
    [/code]
    As before, each time a user updates his status, an activity will be logged. Statuses can be commented upon and also liked. The Mention class is unchanged from Tweetclone. For an in-depth description of this class please refer to Chapter 3.
    [code]
    class Mention
    include DataMapper::Resource
    property :id, Serial
    belongs_to :user
    belongs_to :status
    end

    URL_REGEXP = Regexp.new(‘\b ((https?|telnet|gopher|file|wais|ftp) :
    [\w/#~:.?+=&%@!\-] +?) (?=[.:?\-] * (?: [^\w/#~:.?+=&%@!\-]| $ ))’,
    Regexp::EXTENDED)
    AT_REGEXP = Regexp.new(‘@[\w.@_-]+’, Regexp::EXTENDED)
    [/code]

    Group


    Each user can belong to none, one, or more groups. Each group that is created also belongs to a user and it’s this user that the activity is logged to. Each group has a set of features:

    • A group can have none, one, or more pages.
    • A group has a wall where other users can post to. This wall is created right after the group is created.

    [code]
    class Group
    include DataMapper::Resource

    property :id, Serial
    property :name, String
    property :description, String

    has n, :pages
    has n, :members, :class_name => ‘User’, :through => Resource
    belongs_to :user
    belongs_to :wall

    after :create, :create_wall

    def create_wall
    self.wall = Wall.create
    self.save
    end

    after :create, :add_activity

    def add_activity
    Activity.create(:user => self.user, :activity_type => ‘event’,
    :text => "<a href=’/user/#{self.user.nickname}’>#{self.user.
    formatted_name}</a> created a new group – <a href=’/group/#{self.
    id}’>#{self.name}</a>.")
    end
    end
    [/code]
    Note that the User-Group relationship is a many-to-many relationship, and we use the DataMapper::Resource class as an anonymous class to represent the relationship. For convenience we also provide a method in the User object to retrieve all groups a user’s friends belong to. This becomes useful for us later when suggesting groups for users to join.
    [code]
    def friend_groups
    groups = []
    friends.each do |friend|
    groups += friend.groups
    end
    groups – self.groups
    end
    [/code]

    Event


    Events are quite similar to Groups but with a twist. As before we log it as an activity each time the event is created. Each event has an administrative user who is the person who created the event.
    [code]
    class Event
    include DataMapper::Resource

    property :id, Serial
    property :name, String
    property :description, String
    property :venue, String
    property :date, DateTime
    property :time, Time

    belongs_to :user
    has n, :pages
    has n, :confirms
    has n, :confirmed_users, :through => :confirms, :class_name =>
    ‘User’, :child_key => [:event_id], :mutable => true
    has n, :pendings
    has n, :pending_users, :through => :pendings, :class_name => ‘User’,
    :child_key => [:event_id], :mutable => true
    has n, :declines
    has n, :declined_users, :through => :declines, :class_name =>
    ‘User’, :child_key => [:event_id], :mutable => true

    belongs_to :wall
    after :create, :create_wall

    def create_wall
    self.wall = Wall.create
    self.save
    end

    after :create, :add_activity

    def add_activity
    Activity.create(:user => self.user, :activity_type => ‘event’,
    :text => "<a href=’/user/#{self.user.nickname}’>#{self.user.formatted_
    name}</a> created a new event – <a href=’/event/#{self.id}’>#{self.
    name}</a>.")
    end
    end
    [/code]
    In addition, each event has three types of members depending on their current attendance status:

    • Users confirmed to attend the event
    • Users who are still undecided on attending the event
    • Users who have declined to attend the event

    For this implementation we use a separate class for each type of user, that is we have a Confirm class for confirmed users, a Pending class to indicate users who are undecided, and a Decline class to indicate users who have declined to attend the event.
    [code]
    class Pending
    include DataMapper::Resource
    property :id, Serial
    belongs_to :pending_user, :class_name => ‘User’, :child_key =>
    [:user_id]
    belongs_to :pending_event, :class_name => ‘Event’, :child_key =>
    [:event_id]
    end

    class Decline
    include DataMapper::Resource
    property :id, Serial
    belongs_to :declined_user, :class_name => ‘User’, :child_key =>
    [:user_id]
    belongs_to :declined_event, :class_name => ‘Event’, :child_key =>
    [:event_id]
    end

    class Confirm
    include DataMapper::Resource
    property :id, Serial
    belongs_to :confirmed_user, :class_name => ‘User’, :child_key =>
    [:user_id]
    belongs_to :confirmed_event, :class_name => ‘Event’, :child_key =>
    [:event_id]
    end
    [/code]
    As with Group, we have a convenient method in the User class to help us find the events the user’s friends are attending. We only retrieve confirmed events for this list, which is then sorted according to ascending chronological order.
    [code]
    def friend_events
    events = []
    friends.each do |friend|
    events += friend.confirmed_events
    end
    return events.sort {|x,y| y.time <=> x.time}
    end
    [/code]

    Page


    Pages are a simple means for users to publish their own web pages. A Page can be owned directly by a user, through a group, or through an event.
    [code]
    class Page
    include DataMapper::Resource
    include Commentable
    property :id, Serial
    property :title, String
    property :body, Text property :created_at, DateTime
    has n, :comments
    has n, :likes
    belongs_to :user
    belongs_to :event
    belongs_to :group

    after :create, :add_activity

    def add_activity
    if self.event
    Activity.create(:user => self.user, :activity_type => ‘event
    page’, :text => "<a href=’/user/#{self.user.nickname}’>#{self.user.
    formatted_name}</a> created a page – <a href=’/event/page/#{self.
    id}’>#{self.title}</a> for the event <a href=’/event/#{self.event.
    id}’>#{self.event.name}</a>.")
    elsif self.group
    Activity.create(:user => self.user, :activity_type => ‘group
    page’, :text => "<a href=’/user/#{self.user.nickname}’>#{self.user.
    formatted_name}</a> created a page – <a href=’/group/page/#{self.
    id}’>#{self.title}</a> for the group <a href=’/group/#{self.group.
    id}’>#{self.group.name}</a>.")
    else
    Activity.create(:user => self.user, :activity_type => ‘page’,
    :text => "<a href=’/user/#{self.user.nickname}’>#{self.user.formatted_
    name}</a> created a page – <a href=’/page/#{self.id}’>#{self.title}</
    a>.")
    end
    end
    end
    [/code]
    Page also logs activities according to whichever object that owns it.

    Wall


    A wall is a place where users can place their posts. A wall can belong to a user, event, or group. In fact each time a user, event, or group is created, we will automatically create a wall on its behalf.
    [code]
    class Wall
    include DataMapper::Resource
    property :id, Serial
    has n, :posts
    end
    [/code]
    The implementation of a wall by itself has no definite properties other than being a container for posts. A post is the actual content that a user will submit to a wall and it is something that can be commented and liked. A post on a wall can come from any user, so a post is also associated with the user who created the post.
    [code]
    class Post
    include DataMapper::Resource
    include Commentable
    property :id, Serial
    property :text, Text
    property :created_at, DateTime
    belongs_to :user
    belongs_to :wall
    has n, :comments
    has n, :likes
    end
    [/code]

    Activity


    An activity is a log of a user’s action in Colony that is streamed to the user’s activity feed. Not all actions are logged as activities, for example messages are considered private and are therefore not logged.
    [code]
    class Activity
    include DataMapper::Resource
    include Commentable
    property :id, Serial
    property :activity_type, String
    property :text, Text
    property :created_at, DateTime
    has n, :comments
    has n, :likes
    belongs_to :user
    end
    [/code]
    Activities are commented and can be liked by other users.

    Comment


    Comments in Colony are stored and managed through the Comment class. All usergenerated content including pages, posts, photo, and statuses can be commented by users in Colony. Activities can also be commented on.
    [code]
    class Comment
    include DataMapper::Resource

    property :id, Serial
    property :text, Text
    property :created_at, DateTime
    belongs_to :user
    belongs_to :page
    belongs_to :post
    belongs_to :photo
    belongs_to :activity
    belongs_to :status
    end
    [/code]

    Like


    Like and Comment classes are very similar. The main difference between them is that the Like mechanism is binary (either you like the content or you don’t) whereas you need to provide some content to comment.
    [code]
    class Like
    include DataMapper::Resource
    property :id, Serial
    belongs_to :user
    belongs_to :page
    belongs_to :post
    belongs_to :photo
    belongs_to :activity
    belongs_to :status
    end
    [/code]
    The implementation of the Like mechanism in Colony requires each class of objects that can be liked or commented on to include the Commentable module.
    [code]
    module Commentable
    def people_who_likes
    self.likes.collect { |l| "<a href=’/user/#{l.user.nickname}’>#{l.
    user.formatted_name}</a>" }
    end
    end
    [/code]
    This allows you to retrieve an array of people of who likes the content, which are then formatted as HTML links for easy display.
    This wraps up the data models that we will be using in Colony. In the next chapter we will cover Colony’s application flow and deployment.

    Summary

    This is the second last chapter in this book and also the first one in a series of two chapters describing how we can clone a social networking service like Facebook. Social networking services are the next step in the evolution of Internet applications and Facebook is currently the most successful incarnation of this service. Cloning Facebook is not difficult though, as can be attested in this chapter and also in the many Facebook ‘clones’ out there on the Internet. Let’s look at what we have covered in this chapter.
    First, we went through a whirlwind tour of social networking services and their history, before discussing the most dominant service, Facebook. Next, we described some of its more essential features and we categorized the features into User, Community, and Content sharing features. After that, we went into a high level discussion on these various features and how we implement them in our Facebook clone, Colony. After that, we went briefl y into the various technologies used in the clone.
    After the technology discussion, we jumped straight into the implementation, starting with a detailed discussion of the data models used in this chapter. Our next chapter is the last chapter in this book. We will finish what we have started in this chapter with a detailed step-by-step description of Colony’s application flow, followed with the deployment of Colony on the Heroku cloud platform.

    Filed Under: Ruby Tagged With: Ruby

    Introduction to Ruby On Rails

    October 13, 2010 by Krishna Srinivasan Leave a Comment

    Introduction

    This article provides an introduction to Ruby on Rails. Ruby is a programming language that is interpreted and Rails is a framework written on top of Ruby for writing Web Applications. The article starts with an introduction to Ruby with respect to the basic syntax usage and provides plenty of samples for illustrating conditional constructs and looping constructs. Also the advanced concepts like Classes /Objects and modules will also explained with examples. The later section of the article explains Rails and it starts with the basics of the Model View Controller Architecture for writing Rails application.

    Ruby Introduction

    Ruby is an interpreted language that is easy to use and contains various object oriented features. In this section, we will see the basics of Ruby like variable declaration, conditional constructs, looping, classes/object etc. Readers who are new to ruby can be benefitted with the usage of Ruby in this section.

    Hello Word

    The following code snippet shows how to declare a variable and store some value in it. It later displays the variable’s value in the console.

    [code]hello = "Hello World"
    puts hello[/code]

    Getting input from user

    As seen in the last example, the function puts() can be used to output the content to the console and the method gets() is used to obtain the input from the user.

    [code]puts("Enter a number");
    number = gets();
    puts("The number entered is #{number}");[/code]
    Also have a look at how the variable’s value is used within strings. The pattern used will be ‘#{variableName}’ when this variable is used within double-quotes.

    Declaring Arrays

    Ruby supports the declaration of an array through the following syntax. The index of array location starts from zero.

    [code]my_array = ["one", "two"];
    puts my_array[1];
    puts "Element at location 0 is #{my_array[0]}";

    puts "Length of the array is is #{my_array.length}"

    fruits = Array.new;
    fruits[0] = "Apple";
    fruits[1] = "Orange";
    fruits[2] = "Grapes";

    puts "Length of array is #{fruits.length}"[/code]
    The length of the array – which tells the number of elements that an array is holding currently can be determined by calling ‘arrayObject.length’. Another way for declaring a dynamic array is to call ‘Array.new’ and the populate the elements with the normal syntax.

    Conditional constructs

    The following code snippet illustrates the usage of conditional constructs in Ruby. Note the usage of ‘if’, ‘elsif’ and ‘end’ and the usage of AND operations.

    [code]pizza_price = 80;
    if (pizza_price < 100)
    puts "Price is less";
    elsif (pizza_price > 100 &amp;&amp; pizza_price <200)
    puts "Price is medium";
    else
    puts "Price is large"
    end[/code]
    The above code displays the price based on various ranges just to illustrate the usage of conditional constructs.

    Looping

    The usage of ‘for’ loop is given below. The code declares a array of programming languages and then iterates over the loop using ‘for’ loop.

    [code]languages = ["C", "C++", "Java", ".NET"];

    for language in languages
    puts "Language is #{language}";
    end[/code]

    Function

    Functions are used to define re-usable logic and it can be invoked multiple times. The usage of functions in Ruby is supported through ‘def’ keyword followed by the function name.

    [code]def multiply(a, b)
    result = multiply_with_return(a, b);
    puts("Inside Multiply without return #{result}");
    end

    def multiply_with_return(a, b)

    puts("Multiplying #{a} with #{b}");
    result = a * b;
    return result;
    end

    value = multiply_with_return(10, 10);
    puts("Result from multiplying with return is #{value}");
    multiply(5, 3);[/code]
    Functions can support parameters also. In the above code, we have defined functions ‘multiply’ and ‘multiply_with_return’. The first function uses the parameters passed by the caller and displays the result within the function definition. The second function, after calculating the result, returns the result to the caller.

    Classes and Objects

    In this section, we will see the usage of classes and objects in Ruby. As known, class represents the template for creating objects which contains the combination of related operations and data.

    [code]class Arithmetic
    def initialize(a, b)
    @a = a;
    @b = b;
    end

    def add()
    puts("Addition of #{@a} and #{@b} is #{@a + @b}");
    end

    def sub()
    puts("Subtraction of #{@a} and #{@b} is #{@a – @b}");
    end

    def multiply()
    puts("Multiplication of #{@a} and #{@b} is #{@a * @b}");
    end

    def divide()
    puts("Dividing #{@a} with #{@b} results is #{@a / @b}");
    end

    end

    one_two_arithmetic_object = Arithmetic.new(1, 2);
    one_two_arithmetic_object.add();
    one_two_arithmetic_object.sub();
    one_two_arithmetic_object.multiply();
    one_two_arithmetic_object.divide();[/code]
    In the above code, we declare a class called ‘Arithmetic’ and supports related functions like ‘add’, ‘sub’, ‘multiply’ and ‘divide’ which does the basic mathematical operations. Callers can create objects for this class and can invoke the various available operations supported by this class. There is a special method available in the class declaration called ‘initialize’ which will be invoked automatically when an object is created for this class.
    Also note the syntax for creating an object for the class. It is the class name followed by ‘new’. The parameters following the ‘new’ keyword are the initialization parameters. Here we have passed the initialization arguments ‘1’ and ‘2’ and hence the function ‘initialize’ will be called with these arguments.

    Modules

    Modules in ruby are used to group larger number of operations together. In the below example code, we have created two modules called ‘Number_Operations’ and ‘String_Operations’. Note that these modules are defined in files ‘module_number_operations.rb’ and ‘module_string_operations.rb’.

    [code]module Number_Operations

    def Number_Operations.display()
    puts "Contains the utility operations related to numbers";
    end

    end

    module String_Operations

    def String_Operations.display()
    puts "Contains the utility operations related to string";
    end

    end[/code]
    The below code represents the usage of the modules, and for importing the modules within the client applications, the syntax ‘require ‘, should be used. Then the operations are used through the syntax ‘ModuleName.operationName’.

    [code]require ‘module_string_operations’
    require ‘module_number_operations’

    puts String_Operations.display();
    puts Number_Operations.display();[/code]

    Rails Framework

    Ruby is an interpreted programming language and Rails is a framework written on Ruby for building web applications. Rails prefer the approach on convention over configuration which means that when you create a web application using Rails framework, the supporting tools will provide a lot of sensible defaults so that developers can go and hook in only their customizations instead of writing everything from scratch. Also one can see the how faster it will be in developing web applications through Rails when compared with other popular Web application frameworks.
    Rails framework follows the pattern of MVC which stands for Model-View –Controller architecture which is a popular standard/pattern for building web applications. When a client, typically a browser is making a request, it is the Controller which will be handling the request initially. Based on various request parameters, it will identity the type of request and forwards the request to the appropriate Model objects. There can be multiple Model objects that can be maintained in a Web application and it is the Controller’s responsibility to choose a particular Model object based on various parameters. The Model object represents the action part as well as the data handling part. It may hit the database or can perform any business actions with respect to the context and finally build the data suitable for getting it displayed.
    In this section we will create a Rails starter application that will display some static text in the browser upon client’s request. Rails come with plentiful of utilities that make the developmental tasks easier. Have a look at the following command which will create a web application. The name of the application is ‘hello’ and the ‘new’ subcommand indicates that we want to create a new application.

    [code]rails new hello –O[/code]
    Executing the above command will produce an output similar to the following.

    [code]create
    create README
    create Rakefile
    create config.ru
    create .gitignore
    create Gemfile
    create app
    create app/controllers/application_controller.
    create app/helpers/application_helper.rb
    create app/views/layouts/application.html.erb
    create app/mailers
    create app/models
    create config
    create config/routes.rb
    create config/application.rb
    create config/environment.rb
    create config/environments
    create config/environments/development.rb
    create config/environments/production.rb
    create config/environments/test.rb
    create config/initializers
    create config/initializers/backtrace_silencers
    create config/initializers/inflections.rb
    create config/initializers/mime_types.rb
    create config/initializers/secret_token.rb
    create config/initializers/session_store.rb
    create config/locales
    create config/locales/en.yml
    create config/boot.rb
    create db
    create db/seeds.rb
    create doc
    create doc/README_FOR_APP
    create lib
    create lib/tasks
    create lib/tasks/.gitkeep
    create log
    create log/server.log
    create log/production.log
    create log/development.log
    create log/test.log
    create public
    create public/404.html
    create public/422.html
    create public/500.html
    create public/favicon.ico
    create public/index.html
    create public/robots.txt
    create public/images
    create public/images/rails.png
    create public/stylesheets
    create public/stylesheets/.gitkeep
    create public/javascripts
    create public/javascripts/application.js
    create public/javascripts/controls.js
    create public/javascripts/dragdrop.js
    create public/javascripts/effects.js
    create public/javascripts/prototype.js
    create public/javascripts/rails.js
    create script
    create script/rails
    create test
    create test/performance/browsing_test.rb
    create test/test_helper.rb
    create test/fixtures
    create test/functional
    create test/integration
    create test/unit
    create tmp
    create tmp/sessions
    create tmp/sockets
    create tmp/cache
    create tmp/pids
    create vendor/plugins
    create vendor/plugins/.gitkeep[/code]
    As you can see, the execution of the command creates a standard directory layout containing lots of files and folders. Since Ruby applications are MVC based, the folders ‘app/controllers’, ‘app/models’ and ‘app/views’ contain the files related to Controllers, Models and Views respectively. The ‘db’ folder contains information related to database for various environments like development, testing and production. The ‘configuration’ folder represents the application’s configuration information such as request-action mapping and other dependencies. Test cases related files come under the ‘test’ directory. The ‘public’ directory contains the viewable files that can be accessed directly by the client like html, images etc.
    To launch the ‘hello’ application, go to the directory ‘hello’ (this new directory was created in the previous step) and type the following. Ruby installation comes with a pre-configured web server called ‘WEBrick’ and the following command starts the web server in the default port number ‘3000’.

    [code]rails server
    => Booting WEBrick
    => Rails 3.0.0 application starting in development on http://0.0.0.0:3000
    => Call with -d to detach
    => Ctrl-C to shutdown server
    [2010-10-09 00:36:29] INFO WEBrick 1.3.1
    [2010-10-09 00:36:29] INFO ruby 1.9.2 (2010-08-18) [i386-mingw32]
    [2010-10-09 00:36:29] INFO WEBrick::HTTPServer#start: pid=7816 port=3000[/code]
    As soon as the server is started, the application can be accessed through the url ‘http://localhost:3000’. This shows the default page which is located in the folder ‘/public/index.html’. This application really doesn’t do anything other than creating the basic template for a web application with a default page. Let us add the basic functionality of creating a controller and a view. The controller takes the responsibility of intercepting the client’s request and it will redirect the control to the view. The view takes the control and the content of the view will be displayed to the browser. The following screen-shot will be displayed when the browser is accessed with the url ‘http://localhost:3000’.

    Execute the following command for creating controller and an action. The ‘controller’ subcommand takes the controller name and the action name as arguments. Here the name of the controller is ‘hello’ and the name of the action is ‘hello_action’

    [code]rails generate controller hello hello_action
    create app/controllers/hello_controller.rb
    route get "hello/hello_action"
    invoke erb
    exist app/views/hello
    create app/views/hello/hello_action.html.erb
    invoke test_unit
    identical test/functional/hello_controller_test.rb
    invoke helper
    identical app/helpers/hello_helper.rb
    invoke test_unit
    identical test/unit/helpers/hello_helper_test.rb[/code]
    The execution of the command creates the controller file ‘hello_controller.rb’ in the folder ‘/app/controllers’. Here is the listing for generated ‘hello_controller.rb’ file.

    [code]class HelloController < ApplicationController
    def hello_action
    end
    end[/code]

    In the above code, the ‘HelloController’ class inherits from the base controller Application. A controller can have multiple actions and one such action provided is ‘hello_action’. This makes the application to be accessed through this way ‘/hello/hello_action’. We haven’t provided an implementation for the action and the default implementation is to look for a view file in the directory ‘/app/views//.html.erb’. The generator is sensible enough to create the file ‘hello_action.html.erb’ in the folder ‘/app/views/hello’. The content of this view file is given below,

    [code lang=”html”]
    <h1>Hello</h1>
    <p>This is a starter application to setup things using Rails framework
    [/code]

    This is a starter application to setup things using Rails framework
    Before accessing the application, delete the file ‘index.html’ present in the ‘/public’ directory, the presence of this file overrides the settings. Now accessing the application through the url ‘http://localhost:3000/hello/hello_action’ will display the following view.

     

    Creating Multiple Controllers/Actions and Views

    Now that we have a basic understanding on rails, we will extend the above concepts in creating an application that has multiple controllers, actions and views. This sample will illustrate this example by providing the option on displaying the home page of contacts and messages. We will start creating the application by executing the following command,

    [code]rails new lister –O[/code]
    This creates an application called ‘lister’. Change to the directory of ‘lister’. Now we want to create a controller for ‘contacts’ which will control the home page display and the listing for all contacts. Execute the following command for creating the controller called ‘contacts’.

    [code]rails generate controller contacts contacts_home contacts_view[/code]
    Other than creating a controller called ‘contacts’, the above command will create two more actions called ‘contacts_home’ and ‘contacts_view’. The execution of the above command would result in an output similar to the following.

    [code]create app/controllers/contacts_controller.rb
    route get "contacts/contacts_view"
    route get "contacts/contacts_home"
    invoke erb
    create app/views/contacts
    create app/views/contacts/contacts_home.html.erb
    create app/views/contacts/contacts_view.html.erb
    invoke test_unit
    create test/functional/contacts_controller_test.rb
    invoke helper
    create app/helpers/contacts_helper.rb
    invoke test_unit
    create test/unit/helpers/contacts_helper_test.rb[/code]
    The definition of the ‘contacts_controller’ is shown below. Note that other than the regular controller definitions, two action definitions are added to the file.

    [code]class ContactsController < ApplicationController
    def contacts_home
    end
    def contacts_view
    end
    end[/code]
    The action ‘contacts_home’ is used to display the home page view for contacts. Here is view definition for ‘contacts_home’ found in ‘/app/views/contacts/contacts_view.html/erb’.

    [code lang=”html”]
    <html>
    <head>
    <title>Contacts Home</title>
    </head>
    <body>
    <h1>Contacts Home</h1>
    <br>
    <br>
    <h1>This is the home page for Contacts</h1>
    <br>
    <%= link_to "Go to Contacts View.", :action => "contacts_view" %>
    <br>

    </body>
    </html>
    [/code]
    Note that the above view page defines the link to the contacts view and the usage of ruby scriptlets in the above between the symbols ‘<#=’ and ‘%>’. A predefined element ‘link_to’ is defined containing the action attribute expressed as ‘action’. The display name given to the action is ‘Go to Contacts View’. The action name is given as ‘contacts_view’, note that this action name should match the action name which is defined in the controller. The inclusion of this scriptlet will introduce a link in the html page and by clicking on the link, the user will be redirected to the ‘contacts_view’ page.
    Access to the link ‘http://localhost:3000/contacts/contacts_home’ will display the following page in the browser.

    The code listing for ‘contacts_view’ is given below. Note that this view page has some hard-coded contact entries to get them displayed in the browser. Also the view page includes a reference to the contacts home page.

    All Contacts

    Name Number
    12345 David
    67890 Jones
    13469 Lisa

    [code lang=”html”]
    <html>
    <head>
    <title>Contacts View</title>
    </head>
    <body>
    <h2>All Contacts</h2>

    <table border = "1">
    <tr>
    <th>Name</th>
    <th>Number</th>
    </tr>

    <tr>
    <td>12345</td>
    <td>David</td>
    </tr>

    <tr>
    <td>67890</td>
    <td>Jones</td>
    </tr>
    <tr>
    <td>13469</td>
    <td>Lisa</td>
    </tr>
    </table>
    <br>
    <%= link_to "Go to Contacts home.", :action => "contacts_home" %>
    </body>
    </html>

    [/code]
    Access to the link ‘http://localhost:3000/contacts/contacts_view’ will display the following page in the browser.

    Execute the following command for creating the controller ‘messages’ with two actions ‘messages_home’ and ‘messages_view’.

    [code]rails generate controller messages messages_home messages_view[/code]

    The source code listing for messages_controller ,’messages_home’ and ‘messages_view’ is not included here and it will look similar to the one that we had already seen before. The URLs for accessing the home page and the listing for messages are ‘http://localhost:3000/messages /messages_home’ and ‘http://localhost:3000/messages /messages_view’ respectively.

    Conclusion

    This article provided an introduction to Ruby on Rails explaining the basics in writing Web applications using the Rails framework. Various code samples were discussed to illustrate the basics of Rails framework in writing controllers, actions and views.

    Filed Under: Ruby Tagged With: Rails, Ruby

    Develop Ruby on Rails applications fast using RadRails 1.0 Community Edition

    November 11, 2009 by Krishna Srinivasan Leave a Comment

    Aptana RadRails: An IDE for Rails Development


    Develop Ruby on Rails applications fast using RadRails 1.0 Community Edition


    Coming from a background of developing in languages such as Java, one of the
    things that surprised me the most about the Ruby and Rails community, was the
    common practice of not using an Integrated Development Environment. Most of the
    members of the community, including the most relevant, were comfortable with just a
    programmer’s editor.


    At first I thought it was because, Ruby being a dynamic language, using a full IDE might
    be an overkill. But then I thought of the PHP community, in which several IDEs are
    popular, with PHP also being a dynamic language. So I still had to guess why using an
    IDE was not a common practice within the Ruby on Rails world.


    Nowadays, there is a growing list of IDEs with support for Ruby on Rails, but two
    years ago the options were really scarce. Back then, I chose to use RadRails because it
    worked on top of the Eclipse IDE—which was the tool I was already using for
    other programming languages—and because it was the only free, open source, and
    portable option.


    Truth is, the first version of RadRails I used was very promising, but still a bit too basic.
    It featured just a few specialized tools, Ruby syntax colorization, and a slow and faulty
    code-assistance. As a result, the difference between RadRails and a good programmer’s
    editor was not really significant. However, as Ruby on Rails gained popularity, RadRails
    was vastly improved, and a lot of new features were added.


    At the same time, several other IDEs started to provide support for Ruby too. Today,
    even if many Ruby on Rails developers still don’t use an IDE, a growing number of
    them already.


    During these two years, I’ve been developing projects almost exclusively with Ruby on
    Rails; and I developed all of them using RadRails. Of course I have been keeping an eye
    on every new IDE with Ruby support, just to see if there were any reasons for changing,
    but I still didn’t find any.


    To me, writing this book is a way of contributing back to the RadRails project. I hope this
    book will help the existing community of users of Aptana RadRails, and will also help
    new users to start working with this tool. Besides, thanks to the Packt Open Source
    Project Royalty Scheme, a part of the benefits will be directly paid as a royalty to the
    RadRails project, so by purchasing this book you are funding a bit of the Community
    Edition of Aptana RadRails.


    What This Book Covers


    This book will show you how to get the most of the Community Edition of Aptana
    RadRails for developing Ruby on Rails projects. Apart from the features provided by
    RadRails, the book will give you an overview of working with the Eclipse IDE,
    and will show you how to use the Eclipse functionalities that are relevant for Ruby and
    Rails development.


    This book is not about the Ruby programming language or the Ruby on Rails framework.
    Even if you don’t need to be an expert, you should already be familiar with the language
    and the framework to get the most from this book.


    Chapters 1 and 2 will show you how to install and configure Aptana RadRails, and will
    help you find your way around the Eclipse IDE. If you have previous experience with
    Eclipse , and you have already installed Aptana RadRails, then you can proceed directly
    to Chapter 3.


    Chapters 3 to 8 are a complete reference to each of the components of RadRails,
    including all the configuration options.


    Finally, in Chapter 9 you will find documentation about some complementary plugins
    you can use for connecting to a database and for managing your source repositories.


    You can find below a brief introduction to each of the chapters.


    Chapter 1: This chapter will introduce you the concept of IDE and will give you a
    general overview of what you can expect from Aptana RadRails. You will also find
    instructions about how to install Aptana RadRails and the Eclipse IDE in your system.
    Even if you should already be familiar with the installation of Ruby and Rails, the chapter
    also provides a quick reference for installing Ruby and Ruby on Rails on Windows,
    Linux, and OSX.


    Chapter 2: In most cases, Aptana RadRails will work directly out of the box. However, in
    some cases you will need to make a minimal configuration of the IDE. The first part of
    this chapter will show you the basic configuration of RadRails.


    Chapter 3: Two of the basic tools RadRails provides are the Ruby Explorer and the
    Console View. With the Ruby explorer you will be able to browse the structure of your
    projects and perform any kind of file-related operations, including working with the local
    history of your files. The console view will display the output of most of the processes
    we will launch from RadRails. Apart from learning how to use these views, we will show
    how to use Generators and Rake Tasks from Aptana RadRails to create a simple demo
    application. You will also learn how to start and stop your servers and how to use the
    built-in browser to watch your application in action.


    Chapter 4 explains in detail all the built-in capabilities of RadRails for developing Ruby
    code. You will learn to use the Ruby Editor to write your source code, to navigate
    between the different classes and files, and to get the most out of code completion and the
    code templates.


    Chapter 5: One of the strong points of Aptana RadRails is the great support for the clientside
    of your application: JavaScript, HTML, and CSS. In this chapter you will learn how
    to write Rails views mixing together Ruby code with HTML or JavaScript and getting
    assistance for all of the languages.


    Chapter 6: When an application grows large, it’s always a good idea to have a way of
    debugging the potential errors. This chapter will show you how to use RadRails’ built-in
    debugger for interacting with your code at run time. You will learn to start a server or a
    stand-alone script in debug mode, how to set breakpoints , and how to intercept any Ruby
    exceptions. The debugger will also allow you to walk through your code, to examine the
    values of any variables and expressions, and even to execute arbitrary code at run time by
    using the Display view.


    Chapter 7: Apart from the coding and debugging, Aptana RadRails provides a number of
    specialized tools to make the development and management of your application easier. In
    the context of Eclipse, each of these tools is called a View. In this chapter, you will learn
    how to use the different views to browse the Ruby and Rails documentation, manage and
    monitor your servers, install gems and plugins, launch generators and rake tasks, use code
    annotations, keep track of warnings and to-do lists, evaluate regular expressions, and run
    your tests. If you prefer to use the command line, then you will learn how to take
    advantage of the built-in Rails Shell, in which you can get auto-completion for the most
    used Ruby and Rails commands directly at the command line. This chapter will also
    show you how to use your IDE to control external servers such as Apache or MySQL.


    Chapter 8: Out of the box, Aptana RadRails provides a fully working environment.
    However, many of its components allow for some configuration. This chapter is a
    complete reference to all the preferences you can set to change the user experience when
    using RadRails.


    Chapter 9: Aptana RadRails bundles together plenty of interesting features for the
    developer. However, since the focus is on Ruby on Rails, there are some general aspects
    of the development of a project that are not covered by RadRails. Fortunately, since the
    underlying platform is the Eclipse IDE, we have a virtually unlimited number of
    complementary plugins to choose from. This chapter will give you a general overview of
    the Eclipse plugins ecosystem, and will also explain in detail how to use two of the
    plugins you might want to use when developing. DBViewer is a plugin you can use to
    connect to your database from the IDE. This chapter will show you how to set up the
    plugin, and how to use it for examining and modifying your database structure and
    contents. Subclipse is a plugin to connect to Subversion repositories. By using Subclipse
    you will have repository access directly from your IDE. Besides, the built-in features of
    Subclipse will help you examine and merge changes in a much more comfortable way
    than using the Subversion command line.


    RadRails Views


    By now you should be comfortable with the general interface of Eclipse and
    RadRails. You know already how to create Rails projects, write and debug Ruby code
    and views, and work with HTML, JavaScript, and CSS files. We could say most of
    our programming needs are fulfilled with that.


    When developing a Rails project, there are more things to do than the source code
    itself. We have to start, stop, and monitor our servers, generate code templates, run
    our test suites, install plugins and gems, generate documentation, keep control
    of to-do items, or run Rake tasks for different purposes—database migrations,
    for example.


    RadRails provides different views for supporting these tasks that are a part of
    the development but not of the coding itself. And, of course, it does it so we can
    control everything from within the IDE without having to go back to the
    command-line interface.


    We already had a glimpse of some of these features when using the Generators,
    Rake, or Servers views briefl y when we needed them in previous chapters. Now you
    will learn how to take full advantage of all the RadRails views, to help you take care
    of routine processes and just focus on getting things done.


    Opening the RadRails Views


    Some of the views that we will go through in this chapter are available as part of
    the Rails default perspective, which means you don’t need to do anything special
    to open them; they will appear as tabbed views in a pane at the bottom of your
    workbench. Just look for the tab name of the view you want to see and click on it to
    make it visible.


    However, there are some views that are not opened by default, or maybe you closed
    them at some point accidentally, or maybe you changed to the Debug perspective
    and you want to display some of the RadRails views there. When you need to open
    a view whose tab is not displaying, you can go to the Window menu, and select the
    Show View option.

    If you are in the Rails perspective, all the available views will be displayed in that
    menu, as you can see in the screenshot above. When opening this menu from a
    different perspective, you will not see the RadRails views here, but you can select
    Other… as we did in previous chapters. If this is the case, in the Show View dialog,
    most of the views will appear under the Ruby category, except for the Generators,
    Rails API, and Rake Tasks views, which are located under Rails.


    Documentation Views


    As happens with any modern programming language, Ruby has an extensive
    API. There are lots of libraries and classes and even with Ruby being an intuitive
    language with a neat consistent API, often we need to read the documentation.


    As you probably know, Ruby provides a standard documentation format called
    RDoc, which uses the comments in the source code to generate documentation. We
    can access this RDoc documentation in different ways, mainly in HTML format
    through a browser or by using the command-line tool RI. This produces a plain-text
    output directly at the command shell, in a similar way to the man command in a
    UNIX system.


    RadRails doesn’t add any new functionalities to the built-in documentation, but
    provides some convenient views so we can explore it without losing the context of
    our project’s source.


    Ruby Interactive (RI) View


    This view provides a fast and comfortable way of browsing the local documentation
    in the same way as you would use RI from the command line.

    You can look either for a class or a method name. Just start typing at the input box
    at the top left corner of the view and the list below will display the matching entries.
    That’s a nice improvement over the command line interface, since you can see the
    results as you type instead of having to run a complete search every time.


    If you know the name of both the class and the method you are looking for, then
    you can write them using the hash (pound) sign as a separator. For example, to get
    the documentation for the sum method of the class Enumerable you would write
    Enumerable#sum.


    The documentation will display in the right pane, with a convenient highlighting of
    the referenced methods and classes. Even if the search results of RI don’t look very
    attractive compared to the output of the HTML-based documentation views, RI has
    the advantage of searching locally on your computer, so you can use it even when
    working off-line.

    Pages: 1 2 3 4

    Filed Under: Ruby Tagged With: Rails

    Ruby on Rails Web Mashup Projects

    October 6, 2009 by itadmin Leave a Comment

    Ruby on Rails Web Mashup Projects

    A step-by-step tutorial to building web mashups

    A web mashup is a new type of web application that uses data and services from one or more external sources to build entirely new and different web applications. Web mashups usually mash up data and services that are available on the Internet—freely, commercially, or through other partnership agreements. The external sources that a mashup uses are known as mashup APIs.

    This book shows you how to write web mashups using Ruby on Rails—the new web application development framework. The book has seven real-world projects—the format of each project is similar, with a statement of the project, discussion of the main protocols involved, an overview of the API, and then complete code for building the project. You will be led methodically through concrete steps to build the mashup, with asides to explain the theory behind the code.

    What This Book Covers

    The first chapter introduces the concepts of web mashups to the reader and provides a general introduction to the benefits and pitfalls of using web mashups as standalone applications or as part of existing web applications.

    The first project is a mashup plugin into an existing web application that allows users to find the location of the closest facility from a particular geographic location based on a specified search radius. The location is mapped and displayed on Google Maps.

    The second project is another mashup plugin. This plugin allows users to send messages to their own list of recipients, people who are previously unknown to the website, on behalf of the website. The project uses Google Spreadsheets and EditGrid to aggregate the information, and Clickatell and Interfax to send SMS messages and faxes respectively.

    The third project describes a mashup plugin that allows you to track the sales ranking and customer reviews of a particular product from Amazon.com. The main API used is the Amazon E-Commerce Service (ECS).

    The fourth project shows you how to create a full-fl edged Facebook application that allows a user to perform some of the functions and features of a job board. This mashup uses Facebook, Google Maps, Daylife, Technorati and Indeed.com APIs.

    The fifth project shows you how to create a full web mashup application that allows users to view information on a location. This is the chapter that uses the most mashup APIs, including Google Maps, FUTEF, WebserviceX, Yahoo! Geocoding services, WeatherBug, Kayak, GeoNames, Flickr, and Hostip.info.

    The sixth project describes a mashup plugin that allows an online event ticketing application to receive payment through Paypal, send SMS receipts, and add event records in the customer’s Google Calendar account. The APIs used are Google Calendar, PayPal, and Clickatell.

    The final project shows a complex mashup plugin used for making corporate expense claims. It allows an employee to submit expense claims in Google Docs and Spreadsheets, attaching the claims form and the supporting receipts. His or her manager, also using Google Docs and Spreadsheets, then approves the expense claims and the approved claims are retrieved by the mashup and used to reimburse the employee through
    PayPal. It uses the PayPal APIs and various Google APIs.

    ‘Find closest’ mashup plugin

    What does it do?

    This mashup plugin allows your Rails website or application to have an additional feature that allows your users to find the location of the closest facility from a particular geographic location based on a specified search radius. This mashup plugin integrates with your existing website that has a database of locations of the facilities.

    Building a kiosk locator feature for your site

    Your company has just deployed 500 multi-purpose payment kiosks around the country, cash cows for the milking. Another 500 more are on the way, promising to bring in the big bucks for all the hardworking employees in the company. Naturally your boss wants as many people as possible to know about them and use them. The problem is that while the marketing machine churns away on the marvels and benefits of the kiosks, the customers need to know where they are located to use them. He commands you:


    “Find a way to show our users where the nearest kiosks to him are, and directions to reach them!”

    What you have is a database of all the 500 locations where the kiosks are located, by their full address. What can you do?

    Requirements overview

    Quickly gathering your wits, you penned down the following quick requirements:

    1. Each customer who comes to your site needs to be able to find the closest kiosk to his or her current location.
    2. He or she might also want to know the closest kiosk to any location.
    3. You want to let the users determine the radius of the search.
    4. Finding the locations of the closest kiosks, you need to show him how to reach them.
    5. You have 500 kiosks now, (and you need to show where they are) but another 500 will be coming, in 10s and 20s, so the location of the kiosks need to be specified during the entry of the kiosks. You want to put all of these on some kind of map.

    Sounds difficult? Only if you didn’t know about web mashups!

    Design

    The design for this first project is rather simple. We will build a simple database application using Rails and create a main Kiosk class in which to store the kiosk information including its address, longitude, and latitude information. After populating the database with the kiosk information and address, we will use a geolocation service to discover its longitude and latitude. We store the information in the same table. Next, we will take the kiosk information and mash it up with Google Maps and display the kiosks as pushpins on the online map and place its information inside an info box attached to each pushpin.

    Mashup APIs on the menu

    In this chapter we will be using the following services to create a ‘find closest’ mashup plugin:

    • Google Maps APIs including geocoding services
    • Yahoo geocoding services (part of Yahoo Maps APIs)
    • Geocoder.us geocoding services
    • Geocoder.ca geocoding services
    • Hostip.info

    Google Maps

    Google Maps is a free web-based mapping service provided by Google. It provides a map that can be navigated by dragging the mouse across it and zoomed in and out using the mouse wheel or a zoom bar. It has three forms of views—map, satellite and a hybrid of map and satellite. Google Maps is coded almost entirely in JavaScript and XML and Google provides a free JavaScript API library that allows developers to integrate Google Maps into their own applications. Google Maps APIs also provide geocoding capabilities, that is, they able to convert addresses to longitude and latitude coordinates.

    We will be using two parts of Google Maps:

    • Firstly to geocode addresses as part of GeoKit’s APIs
    • Secondly to display the found kiosk on a customized Google Maps map

    Yahoo Maps

    Yahoo Maps is a free mapping service provided by Yahoo. Much like Google Maps it also provides a map that is navigable in a similar way and also provides an extensive set of APIs. Yahoo’s mapping APIs range from simply including the map directly from the Yahoo Maps website, to Flash APIs and JavaScript APIs. Yahoo Maps also provides geocoding services. We will be using Yahoo Maps geocoding services as part of GeoKit’s API to geocode addresses.

    Geocoder.us

    Geocoder.us is a website that provides free geocoding of addresses and intersections in the United States. It relies on Geo::Coder::US, a Perl module available for download from the CPAN and derives its data from the TIGER/Line data set, public-domain data from the US Census Bureau. Its reliability is higher in urban areas but lower in the other parts of the country. We will be using Geocoder.us as part of GeoKit’s API to geocode addresses.

    Geocoder.ca

    Geocoder.ca is a website that provides free geocoding of addresses in the United States and Canada. Like Geocoder.us. it uses data from TIGER/Line but in addition, draws data from GeoBase, the Canadian government-related initiative that provides geospatial information on Canadian territories. We will be using Geocoder.ca as part of GeoKit’s API to geocode addresses.

    Hostip.info

    Hostip.info is a website that provides free geocoding of IP addresses. Hostip.info offers an HTTP-based API as well as its entire database for integration at no cost. We will be using Hostip.info as part of GeoKit’s API to geocode IP addresses.

    GeoKit

    GeoKit is a Rails plugin that enables you to build location-based applications. For this chapter we will be using GeoKit for its geocoding capabilities in two ways:

    • To determine the longitude and latitude coordinates of the kiosk from its given address
    • To determine the longitude and latitude coordinates of the user from his or her IP address

    GeoKit is a plugin to your Rails application so installing it means more or less copying the source files from the GeoKit Subversion repository and running through an installation script that adds certain default parameters in your environment.rb file.

    To install the GeoKit, go to your Rails application folder and execute this at the command line:

    [code]
    $./script/plugin install svn://rubyforge.org/var/svn/geokit/trunk
    [/code]

    This will copy the necessary files to your RAILS_ROOT/vendor/plugins folder and run the install.rb script.

    Configuring GeoKit

    After installing GeoKit you will need to configure it properly to allow it to work. GeoKit allows you to use a few sets of geocoding APIs, including Yahoo, Google, Geocoder.us, and Geocoder.ca.

    These geocoding providers can be used directly or through a cascading failover sequence. Using Yahoo or Google requires you to register for an API key but they are free. Geocoder.us is also free under certain terms and conditions but both Geocoder.us and Geocoder.ca have commercial accounts. In this chapter I will briefl y go through how to get an application ID from Yahoo and a Google Maps API key from Google.

    Getting an application ID from Yahoo

    Yahoo’s application ID is needed for any Yahoo web service API calls. You can use the same application ID for all services in the same application or multiple applications or one application ID per service.

    To get the Yahoo application ID, go to https://developer.yahoo.com/wsregapp/index.php and provide the necessary information. Note that for this application you don’t need user authentication. Once you click on submit, you will be provided an application ID.

    Getting a Google Maps API key from Google

    To use Google Maps you will need to have a Google Maps API key. Go to http://www.google.com/apis/maps/signup.html. After reading the terms and conditions you will be asked to give a website URL that will use the Google Maps API.

    For geocoding purposes, this is not important (anything will do) but to display Google Maps on a website, this is important because Google Maps will not display if the URL doesn’t match. However all is not lost if you have provided the wrong URL at first; you can create any number of API keys from Google.

    Configuring evironment.rb

    Now that you have a Yahoo application ID and a Google Maps API key, go to environment.rb under the RAILS_ROOT/config folder. Installing GeoKit should have added the following to your environment.rb file:

    [code]
    # Include your application configuration below
    # These defaults are
    used in GeoKit::Mappable.distance_to and in acts_as_mappable
    GeoKit::default_units = :miles
    GeoKit::default_formula = :sphere
    # This is the timeout value in seconds to be used for calls to the
    geocoder web
    # services. For no timeout at all, comment out the setting. The
    timeout unit is in seconds.
    # GeoKit::Geocoders::timeout = 3
    # These settings are used if web service calls must be routed through
    a proxy.
    # These setting can be nil if not needed, otherwise, addr and port
    must be filled in at a minimum. If the proxy requires authentication,
    the username and password can be provided as well.
    GeoKit::Geocoders::proxy_addr = nil
    GeoKit::Geocoders::proxy_port = nil
    GeoKit::Geocoders::proxy_user = nil
    GeoKit::Geocoders::proxy_pass = nil
    # This is your yahoo application key for the Yahoo Geocoder
    # See http://developer.yahoo.com/faq/index.html#appid and
    http://developer.yahoo.com/maps/rest/V1/geocode.html
    GeoKit::Geocoders::yahoo = <YOUR YAHOO APP ID>
    # This is your Google Maps geocoder key.
    # See http://www.google.com/apis/maps/signup.html and
    http://www.google.com/apis/maps/documentation/#Geocoding_Examples
    GeoKit::Geocoders::google = <YOUR GOOGLE MAPS KEY>
    # This is your username and password for geocoder.us
    # To use the free service, the value can be set to nil or false. For
    usage tied to an account, the value should be set to
    username:password.
    # See http://geocoder.us and
    http://geocoder.us/user/signup
    GeoKit::Geocoders::geocoder_us = false
    # This is your authorization key for geocoder.ca.
    # To use the free service, the value can be set to nil or false. For
    usage tied to an account, set the value to the key obtained from
    Geocoder.ca
    # See http://geocoder.ca and
    http://geocoder.ca/?register=1
    GeoKit::Geocoders::geocoder_ca = false
    # This is the order in which the geocoders are called in a failover
    scenario
    # If you only want to use a single geocoder, put a single symbol in
    the array.
    # Valid symbols are :google, :yahoo, :us, and :ca
    # Be aware that there are Terms of Use restrictions on how you can
    use the various geocoders. Make sure you read up on relevant Terms of
    Use for each geocoder you are going to use.
    GeoKit::Geocoders::provider_order = [:google,:yahoo]
    [/code]

    Go to the lines where you are asked to put in the Yahoo and Google keys and change the values accordingly. Make sure the keys are within apostrophes.

    Then go to the provider order and put in the order you want (the first will be tried; if that fails it will go to the next until all are exhausted):

    [code]
    GeoKit::Geocoders::provider_order = [:google,:yahoo]
    [/code]

    This completes the configuration of GeoKit.

    YM4R/GM

    YM4R/GM is another Rails plugin, one that facilitates the use of Google Maps APIs. We will be using YM4R/GM to display the kiosk locations on a customized Google Map. This API essentially wraps around the Google Maps APIs but also provides additional features to make it easier to use from Ruby. To install it, go to your Rails application folder and execute this at the command line:

    [code]
    $./script/plugin install svn://rubyforge.org/var/svn/ym4r/Plugins/GM/trunk/ym4r_gm
    [/code]

    During the installation, the JavaScript files found in the RAILS_ROOT/vendors/plugin/javascript folder will be copied to the RAILS_ROOT/public/javascripts folder.

    A gmaps_api_key.yml file is also created in the RAILS_ROOT/config folder. This file is a YAML representation of a hash, like the database.yml file in which you can set up a test, development, and production environment. This is where you will put in your Google Maps API key (in addition to the environment.rb you have changed earlier).

    For your local testing you will not need to change the values but once you deploy this in production on an Internet site you will need to put in a real value according to your domain.

    What we will be doing

    As this project is a mashup plugin, normally you would already have an existing Rails application you want to add this to. However for the purpose of this chapter, I show how the mashup can be created on a fresh project. This is what we will be doing:

    • Create a new Rails project
    • Install the Rails plugins (GeoKit and YM4R/GM) that will use the various mashup APIs
    • Configure the database access and create the database
    • Create the standard scaffolding
    • Populate the longitude and latitude of the kiosks
    • Create the find feature
    • Display the found kiosk locations on Google Maps

    Creating a new Rails project

    This is the easiest part:

    [code]
    $rails Chapter2
    [/code]

    This will create a new blank Rails project.

    Installing the Rails plugins that will use the various mashup APIs

    In this mashup plugin we’ll need to use GeoKit, a Ruby geocoding library created by Bill Eisenhauer and Andre Lewis, and YM4R/GM—a Ruby Google Maps mapping API created by Guilhem Vellut. Install them according to the instructions given in the section above.

    Next, we need to create the database that we will be using.

    Configuring database access and creating the database

    Assuming that you already know how database migration works in Rails, generate a migration using the migration generator:

    [code]
    $./script/generate migration create_kiosks
    [/code]

    This will create a file 001_create_kiosks.rb file in the RAILS_ROOT/db/migrate folder. Ensure the file has the following information:

    [code]
    class CreateKiosks < ActiveRecord::Migration
    def self.up
    create_table :kiosks do |t|
    t.column :name, :string
    t.column :street, :string
    t.column :city, :string
    t.column :state, :string
    t.column :zipcode, :string
    t.column :lng, :float
    t.column :lat, :float
    end
    end
    def self.down
    drop_table :kiosks
    end
    end
    [/code]

    GeoKit specifies that the two columns must be named lat and lng. These two columns are critical to calculating the closest kiosks to a specific location.

    Now that you have the migration script, run it to create the Kiosk table in your RAILS_ROOT folder:

    Now that you have the migration script, run migrate to create the Kiosk table in your RAILS_ROOT folder:

    [code]
    $rake db:migrate
    [/code]

    This should create the database and populate the kiosks table with a set of data. If it doesn’t work please check if you have created a database schema with your favorite relational database. The database schema should be named chapter2_development. If this name displeases you somehow, you can change it in the RAILS_ROOT/config/database.yml file.

    Creating scaffolding for the project

    You should have the tables and data set up by now so the next step is to create a simple scaffold for the project. Run the following in your RAILS_ROOT folder:

    [code]
    $./script/generate scaffold Kiosk
    [/code]

    This will generate the Kiosk controller and views as well as the Kiosk model. This is the data model for Kiosk, in the kiosk.rb file. This is found in RAILS_ROOT/app/models/.

    [code]
    class Kiosk < ActiveRecord::Base
    def address
    "#{self.street}, #{self.city}, #{self.state}, #{self.zipcode}"
    end
    end
    [/code]

    Just add in the address convenience method to have quick access to the full address of the kiosk. This will be used later for the display in the info box.

    Populating kiosk locations with longitude and latitude information

    Before we begin geolocating the kiosks, we need to put physical addresses to them. We need to put in the street, city, state, and zipcode information for each of the kiosks. After this, we will need to geolocate them and add their longitude and latitude information. This information is the crux of the entire plugin as it allows you to find the closest kiosks.

    In addition you will need to modify the kiosk creation screens to add in the
    longitude and latitude information when the database entry is created.

    Populate the database with sample data

    In the source code bundle you will find a migration file named 002_populate_kiosks.rb that will populate some test data (admittedly less than 500 kiosks) into the system. We will use this data to test our plugin. Place the file in RAILS_ROOT/db/migrate and then run:

    [code]
    $rake db:migrate
    [/code]

    Alternatively you can have some fun entering your own kiosk addresses into the database directly, or find a nice list of addresses you can use to populate the database by any other means.

    Note that we need to create the static scaffold first before populating the database using the migration script above. This is because the migration script uses the Kiosk class to create the records in the database. You should realize by now that migration scripts are also Ruby scripts.

    Bulk adding of longitude and latitude

    One of the very useful tools in Ruby, also used frequently in Rails, is rake. Rake is a simple make utility with rake scripts that are entirely written in Ruby. Rails has a number of rake scripts distributed along with its installation, which you can find out using this command:

    [code]
    $rake –tasks
    [/code]

    Rails rake tasks are very useful because you can access the Rails environment, including libraries and ActiveRecord objects directly in the rake script. You can create your own customized rake task by putting your rake script into the RAILS_ROOT/lib/tasks folder.

    We will use rake to add longitude and latitude information to the kiosks records that are already created in the database.

    Create an add_kiosk_coordinates.rake file with the following code:

    [code]
    namespace :Chapter2 do
    desc ‘Update kiosks with longitude and latitude information’
    task :add_kiosk_coordinates => :environment do
    include GeoKit::Geocoders

    kiosks = Kiosk.find(:all)
    begin
    kiosks.each { |kiosk|
    loc = MultiGeocoder.geocode(kiosk.address)

    kiosk.lat = loc.lat
    kiosk.lng = loc.lng
    kiosk.update
    puts "updated kiosk #{kiosk.name} #{kiosk.address} =>
    [#{loc.lat}, #{loc.lng}]"
    }
    rescue
    puts $!
    end
    end
    end
    [/code]

    In this rake script you first include the Geocoders module that is the main tool for discovering the coordinate information. Then for each kiosk, you find its longitude and latitude and update the kiosk record.

    Run the script from the console in the RAILS_ROOT folder:

    [code]
    $rake Chapter2:add_kiosk_coordinates
    [/code]

    Depending on your network connection (running this rake script will of course require you to be connected to the Internet) it might take some time. Run it over a long lunch break or overnight and check the next day to make sure all records have a longitude and latitude entry. This should provide your mashup with the longitude and latitude coordinates of each kiosk. However your mileage may differ depending on the location of the kiosk and the ability of the geocoding API to derive the coordinates from the addresses.

    Adding longitude and latitude during kiosk creation entry

    Assuming that you have a kiosks_controller.rb already in place (it would be generated automatically along with the rest of the scaffolding), you need to add in a few lines very similar to the ones above to allow the kiosk created to have longitude and latitude information.

    First, include the geocoders by adding GeoKit after the controller definition, in kiosks_controller.rb.

    [code]
    class KiosksController < ApplicationController
    include GeoKit::Geocoders
    [/code]

    Next, add in the highlighted lines in the create method of the controller.

    [code]
    def create
    @kiosk = Kiosk.new(params[:kiosk])
    loc = MultiGeocoder.geocode(@kiosk.address)
    @kiosk.lat = loc.lat
    @kiosk.lng = loc.lng

    if @kiosk.save
    flash[:notice] = ‘Kiosk was successfully created.’
    redirect_to :action => ‘list’
    else
    render :action => ‘new’
    end
    end
    [/code]

    Finally, modify the update method in the controller to update the correct longitude and latitude information if the kiosk location changes.

    [code]
    def update
    @kiosk = Kiosk.find(params[:id])
    address = "#{params[:kiosk][:street]}, #{params[:kiosk][:city]},
    #{params[:kiosk][:state]}"
    loc = MultiGeocoder.geocode(address)
    params[:kiosk][:lat] = loc.lat
    params[:kiosk][:lng] = loc.lng
    if @kiosk.update_attributes(params[:kiosk])
    flash[:notice] = ‘Kiosk was successfully updated.’
    redirect_to :action => ‘show’, :id => @kiosk
    else
    render :action => ‘edit’
    end
    end
    [/code]

    Creating the find closest feature

    Now that you have the kiosk data ready, it’s time to go down to the meat of the code. What you’ll be creating is a search page. This page will have a text field for the user to enter the location from which a number of kiosks closest to it will be displayed. However, to be user-friendly, the initial location of the user is guessed and displayed on the text field.

    Create a search action in your controller (called search.rhtml, and place it in RAILS_ROOT/app/views/kiosks/) to find your current location from the IP address retrieved from your user.

    [code]
    def search
    loc = IpGeocoder.geocode(request.remote_ip)
    @location = []
    @location << loc.street_address << loc.city << loc.country_code
    end
    [/code]

    The remote_ip method of the Rails-provided request object returns the originating IP address, which is used by GeoKit to guess the location from Hostip.info. The location is then used by search.rhtml to display the guessed location.

    Note that if you’re running this locally, i.e. if you are browsing the application from your PC to a locally running server (for example, off your PC as well), you will not get anything. To overcome this, you can use a dynamic DNS service to point an Internet domain name to the public IP address that is assigned to your PC by your ISP. You will usually need to install a small application on your PC that will automatically update the DNS entry whenever your ISP-assigned IP address changes. There are many freely available dynamic DNS services on the Internet.

    When accessing this application, use the hostname given by the dynamic DNS service instead of using localhost. Remember that if you’re running through an internal firewall you need to open up the port you’re starting up your server with. If you have a router to your ISP you might need to allow port forwarding.

    This is a technique you will use subsequently in Chapters 5 and 6.

    Create a search.rhtml file and place it in the RAILS_ROOT/app/view/kiosks folder with the following code:

    [code]
    <h1>Enter source location</h1>
    Enter a source location and a radius to search for the closest kiosk.
    <% form_tag :action => ‘find_closest’ do %>
    <%= text_field_tag ‘location’, @location.compact.join(‘,’) %>
    <%= select_tag ‘radius’, options_for_select({‘5 miles’ => 5, ’10
    miles’ => 10, ’15 miles’ => 15}, 5) %>
    <%= submit_tag ‘find’ %>
    <% end %>
    [/code]

    Here you’re asking for the kiosks closest to a specific location that are within a certain mile radius. We will be using this information later on to limit the search radius.

    After that, mix-in the ActsAsMappable module into the Kiosk model in kiosk.rb.

    [code]
    class Kiosk < ActiveRecord::Base
    acts_as_mappable
    end
    [/code]

    This will add in a calculated column called (by default) distance, which you can use in your condition and order options. One thing to note here is that the ActsAsMappable module uses database-specific code for some of its functions, which are only available in MySQL and PostgresSQL.

    Next, create the find_closest action to determine the location of nearest kiosks.

    [code]
    def find_closest
    @location = MultiGeocoder.geocode(params[:location])
    if @location.success
    @kiosks = Kiosk.find(:all,
    :origin => [@location.lat, @location.lng],
    :conditions => "distance < #{params[:radius]}",
    :order=>’distance’)
    end
    end
    [/code]

    The ActsAsMappable module mixed in also overrides the find method to include an originating location, either based on a geocode-able string or a 2-element array containing the longitude/latitude information. The returned result is a collection of kiosks that are found with the given parameters.

    Finally create a simple find_closest.rhtml view template (and place it in the RAILS_ROOT/app/view/kiosks/ folder) to display the kiosks that are retrieved. We’ll add in the complex stuff later on.

    [code]
    <h1><%= h @kiosks.size %> kiosks found within your search radius</h1>
    <ol>
    <% @kiosks.each do |kiosk| %>
    <li><%= kiosk.name%><br/></li>
    <% end %>
    </ol>
    [/code]

    Do a quick trial run and see if it works.

    [code]
    $./script/server
    [/code]

    Then go to http://localhost:3000/kiosks/search. If you have some data, put in a nearby location (e.g. from our source data: San Francisco) and click on ‘find’. You should be able to retrieve some nearby kiosks.

    Displaying kiosks on Google Maps

    Now that you know where the kiosks are located, it’s time to show them on Google Maps. For this we’ll be using the YM4R/GM plugin. If you haven’t installed this plugin yet, it’s time to go back and install it.

    To add display to Google Maps, you will need to change the find_closest action as well as the find_closest view template. First, add the find_closest action in the kiosks_controller.rb:

    [code]
    def find_closest
    @location = MultiGeocoder.geocode(params[:location])
    if @location.success
    @kiosks = Kiosk.find(:all,
    :origin => [@location.lat, @location.lng],
    :conditions => ["distance < ?", params[:radius]],
    :order=>’distance’)
    @map = GMap.new("map_div")
    @map.control_init(:large_map => true, :map_type => true)
    # create marker for the source location
    @map.icon_global_init( GIcon.new(:image =>
    "http://www.google.com/mapfiles/ms/icons/red-pushpin.png",
    :shadow => "http://www.google.com/
    mapfiles/shadow50.png",
    :icon_size => GSize.new(32,32),
    :shadow_size => GSize.new(37,32),
    :icon_anchor => GPoint.new(9,32),
    :info_window_anchor => GPoint.new(9,2),
    :info_shadow_anchor =>
    GPoint.new(18,25)),
    "icon_source")
    icon_source = Variable.new("icon_source")
    source = GMarker.new([@location.lat, @location.lng],
    :title => ‘Source’,
    :info_window => "You searched for kiosks
    <br>#{params[:radius]} miles around this source",
    :icon => icon_source)
    @map.overlay_init(source)
    # create markers one for each location found
    markers = []
    @kiosks.each { |kiosk|
    info = <<EOS
    <em>#{kiosk.name}</em><br/>
    #{kiosk.distance_from(@location).round} miles away<br/>
    <a href="http://maps.google.com/maps?saddr=#{u(@location.to_
    geocodeable_s)}&daddr=#{u(kiosk.address)}>directions here from
    source</a>
    EOS
    markers << GMarker.new([kiosk.lat, kiosk.lng], :title =>
    kiosk.name, :info_window => info)
    }
    @map.overlay_global_init(GMarkerGroup.new(true, markers),"kiosk_
    markers")
    # zoom to the source
    @map.center_zoom_init([@location.lat, @location.lng], 12)
    end
    end
    [/code]

    Google Maps API is a JavaScript library and YM4R/GM code is a library that creates JavaScript scripts to interact and manipulate the Google Maps API. Almost all classes in the library correspond with an equivalent Google Maps API class, so it is important that you are also familiar with the Google Maps API. The online documentation comes in very useful here so you might want to open up the Google Maps reference documentation (http://www.google.com/apis/maps/documentation/reference.html) as you are coding.

    Let’s go over the code closely.

    The first line creates a GMap object that is placed inside a

    tag with the id map_div while the second line sets some control options.

    [code]
    @map = GMap.new("map_div")
    @map.control_init(:large_map => true, :map_type => true)
    [/code]

    The next few lines then create a GMarker object from the source location that the user entered that uses a specific icon to show it then overlays it on the map. There are several options you can play around with here involving setting the image to be shown as the marker. For this chapter I used a red-colored pushpin from Google Maps itself but you can use any image instead. You can also set the text information window that is displayed when you click on the marker. The text can be in HTML so you can add in other information including images, formatting, and so on.

    [code]
    # create marker for the source location
    @map.icon_global_init( GIcon.new(:image =>
    "http://www.google.com/mapfiles/ms/icons/red-pushpin.png",
    :shadow => "http://www.google.com/
    mapfiles/shadow50.png",
    :icon_size => GSize.new(32,32),
    :shadow_size => GSize.new(37,32),
    :icon_anchor => GPoint.new(9,32),
    :info_window_anchor => GPoint.new(9,2),
    :info_shadow_anchor =>
    GPoint.new(18,25)), "icon_source")
    icon_source = Variable.new("icon_source")
    source = GMarker.new([@location.lat, @location.lng],
    :title => ‘Source’,
    :info_window => "You searched for kiosks
    <br>#{params[:radius]} miles around this source",
    :icon => icon_source)
    @map.overlay_init(source)
    [/code]

    The lines of code after that go through each of the located kiosks and create a GMarker object then overlay it on the map too. For each kiosk location, we put in an info window that describes the distance away from the source location and a link that shows the directions to get from the source to this kiosk. This link goes back to Google and will provide the user with instructions to navigate from the source location to the marked location.

    Note that you need to URL encode the location/address strings of the source and kiosks, so you need to include ERB::Util as well (along with GeoKit::Geocoders). This is the u() method. In kiosks_controller.rb,add:

    [code]
    include ERB::Util
    [/code]

    then add the following (beneath the code entered above):

    [code]
    # create markers one for each location found
    markers = []
    @kiosks.each
    { |kiosk|
    info = <<EOS
    <em>#{kiosk.name}</em><br/>
    #{kiosk.distance_from(@location).round} miles away<br/>
    <a href="http://maps.google.com/maps?saddr=#{u(@location.
    to_geocodeable_s)}&daddr=#{u(kiosk.address)}>directions here from
    source</a>
    EOS
    markers << GMarker.new([kiosk.lat, kiosk.lng],
    :title => kiosk.name, :info_window => info)
    }
    @map.overlay_global_init(GMarkerGroup.new(true, markers),
    "kiosk_markers")
    [/code]

    Finally the last line zooms in and centers on the source location.

    [code]
    # zoom to the source
    @map.center_zoom_init([@location.lat, @location.lng], 12)
    [/code]

    Now let’s look at how the view template is modified to display Google Maps. The bulk of the work has already been done by YM4R/GM so you need only to include a few lines.

    [code lang=”html”]
    <h1><%= h @kiosks.size %> kiosks found within your search radius</h1>
    <ol>
    <% @kiosks.each do |kiosk| %>
    <li><%= kiosk.name%><br/></li>
    <% end %>
    </ol>
    <%= GMap.header %>
    <%= javascript_include_tag("markerGroup") %>
    <%= @map.to_html%>
    <%= @map.div(:width => 500, :height => 450)%>
    [/code]

    Gmap.header creates the header information for the map, including YM4R/GM and Google Maps API JavaScript files. We are also using GMarkerGroups so we need to include the GMarkerGroup JavaScript libraries. Next, we need to initialize the map by calling map.to_html. Finally we’ll need to have a div tag that is the same as the one passed to the GMap constructor in the controller (map_div). This is done by calling the div method of the GMap object. To size the map correctly we will also need to pass on its dimensions (height and width here).

    And you’re ready to roll! Although the page doesn’t display the best layout, you can spice things up by adding the necessary stylesheets to make the view more presentable.

    Summary

    What we’ve learned in this chapter is to create a mashup with Ruby on Rails on a number of mapping and geocoding providers including Yahoo, Google, geocoder. us, geocoder.ca, and hostip.info. We learned to create a mashup that gives us a map of the closest kiosks to a particular location, given an existing database of kiosks that have location addresses. This is just an introduction to the synergistic value that mashups bring to the table, creating value that was not available in individual APIs. When they are all put together, you have a useful feature for your website.

    Filed Under: Ruby Tagged With: Rails

    Building Dynamic Web 2.0 Websites with Ruby on Rails

    September 29, 2009 by itadmin Leave a Comment

    Building Dynamic Web 2.0 Websites with Ruby on Rails

    Ruby on Rails is an open-source web application framework ideally suited to building business applications, accelerating and simplifying the creation of database-driven websites. It has been developed on the Ruby platform.

    This book is a tutorial for creating a complete website with Ruby on Rails (RoR). It will teach you to develop database-backed web applications according to the Model-View-Controller pattern. It will take you on a joy ride right from installation to a complete dynamic website. All the applications discussed in this book will help you add exciting features to your website. This book will show you how to assemble RoR’s features and leverage its power to design, develop, and deploy a fully featured website.

    What This Book Covers

    Chapter 1 gives you an overview of the features of Ruby and RoR, as well as providing the various ways of installing, configuring, and testing both Ruby and RoR.

    Chapter 2 introduces you to the basics of Ruby as well as the main concepts and components of RoR.

    Chapter 3 makes you understand the design of tables according to the conventions of RoR, creation of scaffolds for tables, and changing the scaffolds according to the requirements.

    Chapter 4 gives you details about how to set up the User Management module for the website called TaleWiki.

    Chapter 5 makes you familiar with the Login Management and Comment Management modules for TaleWiki.

    Chapter 6 introduces you to the Migrations and Layouts involved in setting up the template for TaleWiki.

    Chapter 7 describes the tagging functionality being implemented for the enhanced search usability.

    Chapter 8 provides you with the implementation of AJAX for TaleWiki.

    Chapter 9 deals with the development of an interface for the administration.

    Chapter 10 gives you the steps for deploying the website.

    Gathering User Comments

    In the last chapter, we saw how to set up User Management and Role Management for TaleWiki. However, we did not set up the Login Management based on Users. So, it was work only half done. To complete the task, we will set up Login Management in this chapter. It will not only authenticate a user but also provide the session management.

    Secondly, we will look at how to gather user comments for a particular story. We will start with the functionalities to be provided by the Comment Gathering module. We will then move on to the database design for the module. After that we will not only set up the Login Management but also modify the Tale Management so that the User and Tales can be related. We will wrap up with the implementation of the Comment Gathering module. Let’s gets started.

    Understanding the Requirements

    In this chapter, we will be tackling two problems—managing the user authentication as well as the session management and accepting comments from other users for a particular tale. So we can divide the requirements into two:

    • Login Management
    • Comment management

    The Login Management module will also provide the solution to the problem of Tale management that evolved during the development of User management. As the tales table refers to the users table, without a user id a new tale cannot be submitted. The Login management will provide us the user id corresponding to the new tales. Also, it will tell us who has commented on a particular tale. Let us see how.

    Login Management

    As the name suggests, the main functionality the Login Management will provide will be managing the logins. However, managing logins is not a single task. It is dependent on others tasks or operations as well. So, the overall functionalities we will be developing as part of Login management are:

    • Authenticating the User: We can allow only the registered users to access the functionalities of TaleWiki. This operation will ensure that the user is a registered user before he or she tries to enter the TaleWiki.
    • Setting the Session: Once the user is found to be authentic, then we have to maintain his/her authenticity until he/she logs out. The authenticity can be maintained by this functionality.
    • Checking Roles: Each User is assigned a Role. So we will need to check whether a particular functionality—such as viewing details of another user—is a part of the Role. This functionality will check the User’s Role whenever he/she tries to access any functionality provided by TaleWiki.
    • Invalidating Session: When a user logs out, all the details of the user in the current session need to be cleared out. This functionality will clear out all the details of the user, including whether the user is authentic or not.

    Now that we have defined the functionalities of Login management, let us move on to the next set of tasks—managing the comments.

    Managing the Comments

    It is natural for a person to comment upon whatever he or she reads. So, it is necessary to provide a way for users to comment on a particular story. The comments can be of two types—threaded and non-threaded. In threaded comments, one comment can be posted as a response for another comment. If the first comment is removed, then all its child comments will also be removed. If we go for non-threaded
    comments, then each comment is considered an individual. So if one is deleted, others are not affected.

    The Comment Management module will do the same. The functionalities that the Comment Management module will provide are:

    • Adding a Comment: When a user wants to comment on a particular story, he or she can use this functionality. A user can comment on many stories. Comments are not threaded. That means a comment cannot be a response for another comment. Each comment is considered an individual.
    • Deleting a Comment: If an administrator finds a comment offensive or feels that comments are very old, this functionality can be used to delete such comments. Only the administrator will have access to this functionality.
    • Viewing Comments: Using this functionality, a user can read all the comments submitted for a particular story. It will be available for all users. In addition, the comments will be shown in the list view and the details view. In list view, the comments will be shown for each story, and in the details view, all the details including the date and complete text of the comment will be shown.

    We are not providing a way to modify a posted comment. That is because comments are considered one time and brief view of what the user thinks. Hence, no functionality will be provided for the modification of comments. That wraps up the requirements of the Login and Comment Management modules. Next, let us work on the database design for the modules.

    Designing the Database

    As you would have already guessed, our next step will be designing the database. However, unlike the modules that we developed previously, we will be designing the database only for one of the two modules. The Login management module doesn’t require a table because its functionalities are based on the users and roles tables. So we will have to design the table for the Comment management module only. Just like the previous chapter, the steps for designing the database are:

    • Designing the E-R Model
    • Deriving the Schemas
    • Creating the Tables

    Whenever a new module is added, some of the existing E-R models need to be refined, and consequently the corresponding schemas and tables will be changed accordingly. In the case of Comment management, this holds true as you will see as we go through the steps. So here we go.

    Designing the E-R Model

    As the name suggests, the Comment Management module will have data related to the comments submitted by the user. What is this data apart from the comment itself? To answer this, first let us try to define the functionality of the Comment Management module in one line. ‘Comment management will manage comments submitted by a user for a particular story’—that’s how what will look like. The important point here is ‘comments submitted by a user for a particular story’. We have three main entities—Comments, Users, and Stories. Story and User entities have already been discussed in Chapters 3 and 4. So let us look at the Comments entity. The attributes for comments will include the date on which the comment has been added and the title of the comment. In short, the Comments entity will have the following attributes:

    • Id—the unique number to identify each comment
    • Comment body—the text of the comment
    • Date—the date on which comment was added
    • User—the user who has added the comment
    • Story—the story on which the comment has been made

    The entity diagram for the Comments entity will be as follows:

    Coming back to our one line definition, we know that the User, Story, and Comments entities are related. The question is how are they related? The answer is there in the one line definition itself. First, let us consider the User entity. The definition says ‘comments submitted by a user’. That means one user can submit many comments. Hence, the User entity has a one-to-many relationship with the Comments entity. The relationship will be as follows in terms of an E-R diagram:

    The next part of the definition tells us ‘comments for a story’. This means that one story can have many comments. In other words, the Comments entity is related to the Story entity through a many-to-one relationship. The Story entity will be at the ‘one’ end and the Comments entity will be at the ‘many’ end of the relationship. The diagram will look like as follows:

    When looking at all the entities with their attributes and relationships, the picture will be as follows:

    The next step obviously is deriving the schema. Here it comes.

    Deriving the Schema

    We have the complete information about the attributes and relationships of the Comments entity. The main point about this entity is that unlike the User entity it doesn’t introduce any changes in the existing schemas. The reason is that the Comment entity is dependent on other entities and not vice versa. The schema will be as follows:

    Here Story and User both have their own schemas. So their Ids will be the foreign keys in the table. Now, we can develop the table.

    Creating the Tables

    There is only one table to be created. Apart from the attributes, the comments table (keeping with the naming convention), will have two foreign key references—one to the users table and another to the tales table. Including these, the SQL query will be as follows:

    [code]
    CREATE TABLE `comments` (
    `id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY ,
    `comment_body` TEXT NOT NULL ,
    `submission_date` DATE NOT NULL ,
    `tale_id` INT NOT NULL,
    `user_id` INT NOT NULL,
    CONSTRAINT `fk_comments_users` FOREIGN KEY (`user_id`) REFERENCES
    users( `id`) ,
    CONSTRAINT `fk_comments_tales` FOREIGN KEY (`tale_id`) REFERENCES
    tales( `id`)
    ) ENGINE = innodb;
    [/code]

    That completes the table definition. By this time you will have started to think that if RoR is so productive, why do we still have to use the SQL statements to create tables?. There is another way—the Ruby way. We will see that in the next chapter where we will convert all the table creation statements using Ruby. Now that the required table has been defined, let us develop the modules starting with the Login management.

    Developing the Login Management Module

    Even though Login and session handling are separate functionalities from User management, they depend on the same table—user. Also, the functionalities are more alike than different. Hence, instead of creating a new Controller, we will be using the UserController itself as the Controller for the Login module. Keeping this point in mind, let us look at the steps involved in developing the Login Management, which are:

    • Creating the Login page
    • Implementing the Authentication Method
    • Setting up the Session
    • Applying Authorization

    Leaving aside the first step, all other steps mainly focus on the Controller. Here we go.

    Creating the Login Page

    We need a login page with textboxes for user name and password in which users can put their credentials and submit to the login authenticator (fancy name for the action method that will contain the logic to authenticate the user). That’s what we are going to create now. The convention for any website is to show the login page when the user enters the URL without any specific page in mind. RoR also follows this convention.
    For example, if you enter the URL as http://localhost:3000/user, it displays the list of users. The reason is that the index action method of the UserController class calls the list method whenever the aforementioned URL is used. From this, we can understand two things—first, the default action method is index, and second, the first page to be shown is changeable if we change the index method.

    What we need is to show the login page whenever a user enters the URL http://localhost:3000/user. So let’s change the index method. Open the user_controller.rb file from the app/views/user folder and remove all the statements from the body of the index method so that it looks like as follows:

    [code]
    def index
    end
    [/code]

    Next, let us create an index.rhtml file, which will be shown when the index method is called. This file will be the login page. In the app/views/user folder, create an index.rhtml file. It will be as follows:

    [code]
    <%= form_tag :action=> ‘authenticate’%>
    <table >
    <tr align="center" class="tablebody">
    <td>User name:</td>
    <td><%= text_field("user", "user_name",:size=>"15" ) %></td>
    </tr>
    <tr align="center" class="tablebody">
    <td>Password:</td>
    <td><%= password_field("user",
    "password",:size=>"17" ) %></td>
    </tr>
    <tr align="center" class="tablebody">
    <td></td>
    <td><input type="submit" value=" LOGIN " /></td>
    </tr>
    </tabale>
    [/code]

    It uses two new form helpers—text_field and password_field. The text_field creates a text field with the name passed as the parameter, and the password_field creates a password field again with the name passed as the parameter. We have passed the authenticate method as the action parameter so that the form is submitted to the authenticate method. That completes the login page creation. Next, we will work on the authenticate method.

    Implementing the Authenticate method

    Authenticating a user essentially means checking whether the user name and password given by the user corresponds to the one in database or not. In our case, the user gives us the user name and password through the login page. What we will be doing is checking whether the user is in database and does the password that we got corresponds to the password stored in the database for the user? Here, we will be working on two levels:

    • Model
    • Controller

    We can put the data access part in the action method that being the Controller itself. But it will create problems in the future if we want to add something extra to the user name/password checking code. That’s why we are going to put (or delegate) the data access part into Model.

    Model

    We will be modifying the User class by adding a method that will check whether the user name and password provided by the user is correct or not. The name of the method is login. It is as follows:

    [code]
    def self.login(name,password)
    find(:first,:conditions => ["user_name = ? and password =
    ?",name, password])
    end
    [/code]

    It is defined as a singleton method of the User class by using the self keyword. The singleton methods are special class-level methods. The conditions parameter of the find method takes an array of condition and the corresponding values. The find method generates an SQL statement from the passed parameters. Here, the find method finds the first record that matches the provided user_name and password. Now, let us create the method that the Controller will call to check the validity of the user. Let us name it check_login. The definition is as follows:

    [code]
    def check_login
    User.login(self.user_name, self.password)
    end
    [/code]

    This function calls the login method. Now if you observe closely, check_login calls the login function. One more point to remember—if a method ‘test’ returns a value and you call ‘test’ from another method ‘test1,’ then you don’t need to say ‘return test’ from within ‘test1’.The value returned from ‘test’ will be returned by ‘test1’ implicitly. That completes the changes to be done at the Model level. Now let us see the changes at the Controller-level.

    Controller

    In the Controller for User—UserController—add a new method named authenticate. The method will first create a User object based on the user name and password. Then it will invoke check_login on the newly created User object. If check_login is successful, that is, it does not return nil, then the user is redirected to the list view of Tales. Otherwise, the user is redirected to the login page itself. Here is what the method will look like:

    [code]
    def authenticate
    @user = User.new(params[:user])
    valid_user = @user.check_login
    if logged_in_user
    flash[:note]="Welcome "+logged_in_user.name
    redirect_to(:controller=>’tale’,:action => "list")
    else
    flash[:notice] = "Invalid User/Password"
    redirect_to :action=> "index"
    end
    end
    [/code]

    The redirect_to method accepts two parameters—the name of the Controller and the method within the Controller. If the user is valid, then the list method of TaleController is called, or in other words, the user is redirected to the list of tales. Next, let us make it more robust by checking for the get method. If a user directly types a URL to an action, then the get method is received by the method. If any user does that, we want him/her to be redirected to the login page. To do this, we wrap up the user validation logic in an if/else block. The code will be the following:

    [code]
    def authenticate
    if request.get?
    render :action=> ‘index’
    else
    @user = User.new(params[:user])
    valid_user = @user.check_login
    if valid_user
    flash[:note]="Welcome "+valid_user.user_name
    redirect_to(:controller=>’tale’,:action => ‘list’)
    else
    flash[:notice] = "Invalid User/Password"
    redirect_to :action=> ‘index’
    end
    end
    end
    [/code]

    The get? method returns true if the URL has the GET method else it returns false. That completes the login authentication part. Next, let us set up the session.

    In Ruby, any method that returns a Boolean value—true or false—is suffixed with a question mark (?). The get method of the request object returns a boolean value. So it is suffixed with a question mark (?).

    Setting up the Session

    Once a user is authenticated, the next step is to set up the session to track the user. Session, by definition, is the conversation between the user and the server from the moment the user logs in to the moment the user logs out. A conversation is a pair of requests by the user and the response from the server. In RoR, the session can be tracked either by using cookies or the session object. The session is an object provided by RoR. The session object can hold objects where as cookies cannot. Therefore, we will be using the session object. The session object is a hash like structure, which can hold the key and the corresponding value. Setting up a session is as easy as providing a key to the session object and assigning it a value. The following code illustrates this aspect:

    [code]
    def authenticate
    if request.get?
    render :action=> ‘index’
    else
    @user = User.new(params[:user])
    valid_user = @user.check_login
    if valid_user
    session[:user_id]=valid_user.id
    flash[:note]="Welcome "+valid_user.user_name
    redirect_to(:controller=>’tale’,:action => ‘list’)
    else
    flash[:notice] = "Invalid User/Password"
    redirect_to :action=> ‘index’
    end
    end
    end
    [/code]

    That completes setting up the session part. That brings us to the last step—applying authorization.

    Applying Authorization

    Until now, we have authenticated the user and set up a session for him/her. However, we still haven’t ensured that only the authenticated users can access the different functionalities of TaleWiki. This is where authorization comes into the picture. Authorization has two levels—coarse grained and fine grained. Coarse grained authorization looks at the whole picture whereas the fine grained authorization looks at the individual ‘pixels’ of the picture. Ensuring that only the authenticated users can get into TaleWiki is a part of coarse grained authorization while checking the privileges for each functionality comes under the fine grained authorization. In this chapter, we will be working with the coarse grained authorization.

    The best place to apply the coarse grained authorization is the Controller as it is the central point of data exchange. Just like other aspects, RoR provides a functionality to easily apply any kind of logic on the Controller as a whole in the form of filters. To jog your memory, a filter contains a set of statements that need to be executed before, after (or before and after) the methods within the Controllers are executed.

    Our problem is to check whether the user is authenticated or not, before any method in a Controller is executed. The solution to our problem is using a ‘before filter’. But we have to apply authorization to all the Controllers. Hence, the filter should be callable from any of the Controller. If you look at the definition of a Controller, you can find such a place. Each Controller is inherited from the ApplicationController. Anything placed in ApplicationController will be callable from other Controllers. In other words, any method placed in ApplicationController becomes global to all the Controllers within your application. So, we will place the method containing the filter logic in ApplicationController.

    To check whether a user is authentic or not, the simplest way is to check whether a session exists for that person or not. If it exists, then we can continue with the normal execution. Let us name it check_authentic_user. The implementation will be as follows:

    [code]
    def check_authentic_user
    unless session[:user_id]
    flash[:notice] = "Please log in"
    redirect_to(:controller => "user", :action =>
    "index")
    end
    end
    [/code]

    It checks for the user_id key in a session. If it is not present, the user is redirected to the login page. Place the code in the application.rb file as a method of ApplicationController. Next, let us use it as a filter. First, we will tell UserController to apply the filter for all the action methods except index and authenticate methods. Add the following statement to the UserController. It should be the first statement after the starting of the Controller class.

    [code]
    class UserController < ApplicationController
    before_filter :check_authentic_user, :except =>[ :index, :authenticate
    ]
    :
    :
    end
    [/code]

    Similarly, we will place the filter in other Controllers as well. However, in their case, there are no exceptions. So TaleController will have:

    [code]
    class TaleController < ApplicationController
    before_filter :check_authentic_user
    :
    :
    end
    [/code]

    GenreController and RoleController will be the same as TaleController. Thus, we have completed the ‘applying authorization’ part for the time being. Now, let’s tie up one loose end—the problem of adding a new tale.

    Tying Up the Loose Ends

    When we developed the User management, the Tale management was affected as the tales table has a many-to-one relationship with the users table. Now we can solve the problem created by the foreign key reference. First, open the user.rb file and add the following statement indicating that it is at the ‘one’ end of the relationship:

    [code]
    has_many :tale
    [/code]

    After addition of the statement, the class will look like the following:

    [code]
    class User < ActiveRecord::Base
    validates_presence_of :user_name, :password, :first_name,
    :last_name, :age, :email, :country
    validates_uniqueness_of :user_name
    validates_numericality_of :age
    validates_format_of :email, :with => /\A([^@\s]+)@((?:[-a-
    z0-9]+\.)+[a-z]{2,})\Z/i
    belongs_to :role
    has_many :tale
    def check_login
    User.login(self.name, self.password)
    end
    def self.login(name,password)
    find(:first,:conditions => ["user_name = ? and password
    =?",name, password])
    end
    end
    [/code]

    Next, add the following statement to the tale.rb file:

    [code]
    belongs_to :user
    [/code]

    The file will look like as follows:

    [code]
    class Tale < ActiveRecord::Base
    validates_presence_of :title, :body_text, :source
    belongs_to:genre
    belongs_to :user
    end
    [/code]

    Next, open the tale_controller.rb file. In the create method, we need to add the user’s id to the tale’s user id reference so that the referential integrity can be satisfied. For that, we will get the current user’s id from the session and set it as the value of the user_id attribute of the tale object. The create method will look like as follows, after doing the changes:

    [code]
    def create
    @tale = Tale.new(params[:tale])
    @tale.genre_id=params[:genre]
    @tale.user_id=session[:user_id]

    @tale.status="new"
    if @tale.save
    flash[:notice] = ‘Tale was successfully created.’
    redirect_to :action => ‘list’
    else
    render :action => ‘new’
    end
    end
    [/code]

    That’s it. The ‘loose ends’ related to the User management are tied up. Now let us move onto the Comment Management module.

    Developing the Comment Management Module

    From the description of functionalities, we know that the module needs to support only three operations—add, view, and delete. The steps for developing the module are almost the same:

    • Generating the Scaffold
    • Modifying the Model
    • Refining the View
    • Customizing the Controller

    We have changed the order of refining the view and customizing the Controller steps. That’s what I meant by ‘almost the same’. Let’s get into the development.

    Generating the Scaffold

    Open the RoR prompt using use_ruby command, and enter the following command:

    [code]
    C:\InstantRails\rails_apps\talewiki>ruby script/generate scaffold Comment comment list show new create destroy
    [/code]

    You will get the following screen:

    If the scaffold command is reused, then it will not rewrite the existing files unless you specify the -force parameter. We need only new, list, and delete functionalities. So, we have specified the actions that we need—list, show for listing of comments, new and create for adding, and delete for deleting. However, it will still create the stubs and links that need to be tackled at the View level. First, let us do the required modifications at the Model level.

    Modifying the Model

    First, we have to tell RoR which fields should not be empty. For that, add the validates_presence_of method with :comment_body as the argument in the comment.rb file. After addition, the code shall be as follows:

    [code]
    class Comment < ActiveRecord::Base
    validates_presence_of :comment_body
    end
    [/code]

    Next, we have to tell that the comments table is at the ‘many’ end of the relationship with both tales and users table. For that, add a belongs_to declaration to the comment.rb file.

    [code]
    class Comment < ActiveRecord::Base
    validates_presence_of :comment_body
    belongs_to :tale
    belongs_to :user
    end
    [/code]

    The next step is to tell both the users and the tales table that they are at the ‘one’ end of the relationship. For that, open the user.rb and tale.rb files, and add the has_many declaration. After the additions, the code will be as follows for user.rb:

    [code]
    class User < ActiveRecord::Base
    validates_presence_of :user_name, :password, :first_name,
    :last_name, :age, :email, :country
    validates_uniqueness_of :user_name
    validates_numericality_of :age
    validates_format_of :email, :with => /\A([^@\s]+)@((?:[-az0-
    9]+\.)+[a-z]{2,})\Z/i
    belongs_to :role
    has_many :tale
    has_many :comment
    def check_login
    User.login(self.name, self.password)
    end

    def self.login(name,password)
    find(:first,:conditions => ["user_name = ? and password
    =?",name, password])
    end
    end
    [/code]

    For tale.rb, here is the code:

    [code]
    class Tale < ActiveRecord::Base
    validates_presence_of :title, :body_text, :source
    belongs_to:genre
    belongs_to :user
    has_many :comment
    end
    [/code]

    That completes the changes to be done at the Model level. Next, let us refine the View.

    Refining the View

    Comments will be given for a story. That means the page displaying a tale will have a link to add comments. This also means that the Comment management module is not a ‘standalone’ module like others, as it will not have its own menu when we decide upon the template. Now coming back to links to the comments in the tale display page, for what functionalities do we need the links? The answer is two—adding a comment and listing the comment. The add comment link will lead to the ‘New Comment’ page, and the view comments link will lead to the list view of the comments. Now let us see what are the problems—each comment needs a user id and the id of the tale for which the comment is being added. The listing of comments needs only the id of the tale. As user id is available from the session, we have to add only the tale id as a part of the link. That is what we are going to do.

    Open the show.rhtml file from the app/views/tale directory. It contents are as follows

    [code lang=”html”]
    <% for column in Tale.content_columns %>
    <p>
    <%= column.human_name %>: <%=h @tale.send(column.name) %>
    </p>
    <% end %>
    <%= link_to ‘Edit’, :action => ‘edit’, :id => @tale %>
    <%= link_to ‘Back’, :action => ‘list’ %>
    [/code]

    Now let us add two more links—one for adding a comment and another for listing the comments:

    [code lang=”html”]
    <% for column in Tale.content_columns %>
    <p>
    <%= column.human_name %>: <%=h @tale.send(column.name) %>
    </p>
    <% end %>

    <%= link_to ‘Edit’, :action => ‘edit’, :id => @tale %>
    <%= link_to ‘Back’, :action => ‘list’ %>
    <%= link_to ‘Add Comment’,:controller=>’comment’, :action => ‘new’, :
    id => @tale.id %>
    <%= link_to ‘View Comments’,:controller=>’comment’, :action => ‘list’,
    :id => @tale.id %>
    [/code]

    The next change we have to do is remove the edit option from the viewing part of the comments. So open the list.rhtml file from the app/views/comments. The code will be as follows:

    [code lang=”html”]
    <h1>Listing comments</h1>
    <table>
    <tr>
    <% for column in Comment.content_columns %>
    <th><%= column.human_name %></th>
    <% end %>
    </tr>

    <% for comment in @comments %>
    <tr>
    <% for column in Comment.content_columns %>
    <td><%=h comment.send(column.name) %></td>
    <% end %>
    <td><%= link_to ‘Show’, :action => ‘show’, :id => comment %></td>
    <td><%= link_to ‘Edit’, :action => ‘edit’, :id => comment %></td>
    <td><%= link_to ‘Destroy’, { :action => ‘destroy’, :id => comment
    }, :confirm => ‘Are you sure?’, :method => :post %></td>
    </tr>
    <% end %>
    </table>
    <%= link_to ‘Previous page’, { :page => @comment_pages.current.
    previous } if @comment_pages.current.previous %>
    <%= link_to ‘Next page’, { :page => @comment_pages.current.next } if @
    comment_pages.current.next %>
    <br />
    <%= link_to ‘New comment’, :action => ‘new’ %>
    [/code]

    Delete the tags that link to the Edit and New Comment functionalities. We do not need anyone adding a comment without reading the story. After deletions, the code will be as follows:

    [code lang=”html”]
    <h1>Listing comments</h1>
    <table>
    <tr>
    <% for column in Comment.content_columns %>
    <th><%= column.human_name %></th>
    <% end %>
    </tr>
    <% for comment in @comments %>
    <tr>
    <% for column in Comment.content_columns %>
    <td><%=h comment.send(column.name) %></td>
    <% end %>
    <td><%= link_to ‘Show’, :action => ‘show’, :id => comment %></td>
    <td><%= link_to ‘Destroy’, { :action => ‘destroy’, :id => comment
    }, :confirm => ‘Are you sure?’, :method => :post %></td>
    </tr>
    <% end %>
    </table>
    <%= link_to ‘Previous page’, { :page => @comment_pages.current.
    previous } if @comment_pages.current.previous %>
    <%= link_to ‘Next page’, { :page => @comment_pages.current.next } if @
    comment_pages.current.next %>
    <br />
    [/code]

    That completes the refinement to be done to the VIEW. Now let’s modify the Controller.

    Customizing the Controller

    Open the comment_controller.rb file and in the new method add the tale_id to the session object so that the method looks like the following:

    [code]
    def new
    @comment = Comment.new
    session[:tale_id]=params[:id]
    end
    [/code]

    Now in the create method, let us get the tale_id and the user_id from the session, and pass it to the comment object. We have used the session object because the tale_id is coming as a part of the get request, which will be available only to the new method and not the create method. After the changes, the create method will be as follows:

    [code]
    def create
    @comment = Comment.new(params[:comment])
    @comment.tale_id=session[:tale_id]
    @comment.user_id=session[:user_id]
    if @comment.save
    flash[:notice] = ‘Comment was successfully created.’
    redirect_to :action => ‘list’
    else
    render :action => ‘new’
    end
    end
    [/code]

    We do not want to show the list of comments, once a comment has been added. Therefore, we will redirect the user to the tale’s list once a comment has been added successfully.

    [code]
    def create
    @comment = Comment.new(params[:comment])
    @comment.user_id=session[:user_id]
    @comment.tale_id=session[:tale_id]
    if @comment.save
    flash[:notice] = ‘Comment was successfully created.’
    redirect_to :controller=>’tale’, :action => ‘list’
    else
    render :action => ‘new’
    end
    end
    [/code]

    Apart from this, we have to change the list method so that it finds that only those
    comments are selected for which the tale_id has been passed through the link. So
    let us modify the paginate method in the list method to add a condition. After
    modification, the list method will be as follows:

    [code]
    def list
    @comment_pages, @comments = paginate :comments, :
    conditions=>[‘tale_id = ?’,
    params[:id]] :per_page => 10
    end
    [/code]

    As you can see, the paginate method takes the table to paginate, the condition which is optional and the number of items to be shown per page as arguments.

    And that completes our current work on the Comment management module. Now it is testing time!

    Testing the Module

    Let us start with the authorization part. Give the following URL at the address bar:

    [code]
    http://localhost:3000/tale
    [/code]

    If you get the following screen, it means authorization is working fine:

    Next let us test the login functionality. Firstly, give the wrong User name and Password (give anything). If you get the following screen, then the changes are working fine:

    Now, give the correct User name/Password combination. I am giving tester as User name and testing as password. If you get the following screen, then authentication is working fine, and also the redirection is doing what it is supposed to do.

    Now click on the list link of the first tale and you will get the following screen:

    On the detail page, click on the Add Comment link. The following screen will be displayed:

    Give the following inputs:

    Comment Body—This is a test.

    Submission Date—(leave the default date)

    Now click on Create. Then, if you get the following screen you can rest assured that everything is working as planned.

    Now click on the Show link again and select the View Comments link. If you get the following screen, then the functionality is working:

    These tests tell us that the changes we did are working fine. And that completes our ‘endeavour’ on gathering the user comments.

    Summary

    We have completed Login management and Comment management. Login management was one of the loose ends from the User management part. Now we can concentrate on enhancing the developed modules. These enhancements include custom template creation, the logout option, database-independent table creation, and other features that need to be completed before moving on to developing the new functionalities. These enhancements will implemented in the next chapter. So keep reading!

    Filed Under: Ruby Tagged With: Rails

    Follow Us

    • Facebook
    • Pinterest

    As a participant in the Amazon Services LLC Associates Program, this site may earn from qualifying purchases. We may also earn commissions on purchases from other retail websites.

    JavaBeat

    FEATURED TUTORIALS

    Answered: Using Java to Convert Int to String

    What is new in Java 6.0 Collections API?

    The Java 6.0 Compiler API

    Copyright © by JavaBeat · All rights reserved