You are on page 1of 126

Page 1 of 126

Does an Object Oriented MVC paradigm get Web


Application Developers on track for Web 2.0?

A study with Ruby on Rails.

P Shearan

MSc Internet and Multimedia Systems

October 2007
Faculty of Business, Computing & Information Management

London South Bank University


Page 2 of 126

ABSTRACT

The advent of AJAX, as a client-side technology, has heralded the introduction of


Web 2.0, an environment of dynamic content, aimed at sharing information and
collaboration, and producing desktop-like applications made available through web
browsers. Web developers need to investigate the new technologies required for
practical deployment of Web 2.0 and to prepare for Web 3.0. The majority of
current web applications have been developed in transactional languages (PHP,
PERL, ASP) but need to be moved to object oriented web Model-View-Controller
(MVC) frameworks to allow developers to implement the new techniques in Web
2.0. Through assessment and development of a web application in the Ruby on Rails
MVC framework, it has been demonstrated that the deployment of Web 2.0
technology becomes quickly available and repeatable. This allows the flexibility and
speed of adoption of new features that customers of web application developers
desire to stay competitive in the fast moving marketplace of web based business.
During the research it has been highlighted that the transition to a framework such
as Ruby on Rails could take as long as two months before a web application
developer is fully productive in this new environment and that Ruby on Rails suffers
from inconsistent sources of documentation. However, the benefits of fast and
repeatable delivery of a Web 2.0 application in an object oriented web MVC
framework like Ruby on Rails appear to outweigh the issues of migration to the new
development platform from the existing transactional environment.
Page 3 of 126

ACKNOWLEDGEMENTS

Bellsy, without your constant support, encouragement and editing this never would have
happened.

Thommo, thanks for the kick up the “you know where” when I needed it most.

Mark Josephs, my tutor, thank you for all the invaluable support, help and input.

“Full fathom five thy father lies;

Of his bones are coral made;

Those are pearls that were his eyes:

Nothing of him that doth fade

But doth suffer a sea-change

Into something rich and strange.”

Ariel‟s Song, Tempest by William Shakespeare


Page 4 of 126

Table of Contents
1 INTRODUCTION .............................................................................................................................. 7

2 THEORETICAL CONCEPTS: EVOLUTION OF THE WEB ....................................................10

2.1 WEB 1.0 TO 2.0 AND EVEN 3.0. THE CHANGING LANDSCAPE .......................................10
2.1.1 Web 1.0 ..................................................................................................................................10
2.1.2 Web 2.0 ..................................................................................................................................11
2.1.3 Web 3.0 ..................................................................................................................................13
2.2 WHERE ARE WE NOW? ..........................................................................................................14
2.2.1 What currently runs the Server-Side of the Web? ..................................................................14
2.2.2 Web Application and Systems Development Tools ................................................................15
2.2.3 Comparing Typical Web 1.0 and Web 2.0 Application Architecture .....................................17
2.3 WEB 2.0 APPLICATIONS IN DETAIL ....................................................................................19
2.3.1 A Business Layman’s view of Web 2.0 ...................................................................................19
2.3.2 Web 2.0 – the O’Reilly Way ...................................................................................................19
2.3.3 Mapping Technology to Web 2.0 Services .............................................................................21
2.3.4 Breaking the Page .................................................................................................................21
2.3.5 Tim O’Reilly’s Hierarchy of “Web 2.0-ness”........................................................................22
2.3.6 Ajax ........................................................................................................................................22
2.3.7 Blogs ......................................................................................................................................22
2.3.8 Mashups .................................................................................................................................23
2.3.9 Tagging ..................................................................................................................................24
2.3.10 Web Services .....................................................................................................................24
2.3.11 Web Syndicated Feeds (RSS and Atom) ............................................................................25
2.3.12 Wikis .................................................................................................................................27
2.4 THE WEB DEVELOPERS CONUNDRUM .............................................................................27

2.5 CREATING THE ACTIVE DESKTOP IN WEB 2.0 .................................................................28


2.5.1 AJAX – the great misnomer ...................................................................................................28
2.5.2 Prototype and Script.aculo.us................................................................................................31
2.5.3 How AJAX works ...................................................................................................................32
3 SELECTING THE DEVELOPMENT ENVIRONMENT .............................................................34

3.1 WEB 2.0 DEVELOPMENT FRAMEWORKS ..........................................................................34


3.1.1 What is the “framework”.......................................................................................................34
3.1.2 MVC – the Model-View-Controller .......................................................................................34
3.1.3 Object-Oriented vs Procedural Development Paradigms .....................................................36
3.1.4 Object Oriented Code – Relevance to Web Design ...............................................................37
3.1.5 Why objects are good.............................................................................................................37
3.1.6 Potential Frameworks: CakePHP, Django, and Ruby on Rails ............................................38
3.1.7 Choosing Ruby On Rails ........................................................................................................39
3.2 RUBY ON RAILS ......................................................................................................................41
3.2.1 History of Ruby on Rails ........................................................................................................41
3.2.2 Advantages of Ruby on Rails .................................................................................................43
3.2.3 Test Driven Development with Ruby on Rails .......................................................................45
4 PRACTICAL APPLICATION RESEARCH PROJECT ..............................................................48

4.1 ABOUT THE PRACTICAL APPLICATION ............................................................................48


Page 5 of 126

4.1.1 Aims of the practical project..................................................................................................48


4.1.2 Time taken to learn new development environment ...............................................................49
4.1.3 Applied Research - practical application overview ...............................................................49
4.2 PREPARING TO IMPLEMENT THE PRACTICAL APPLICATION .....................................50
4.2.1 Agile Development Process ...................................................................................................50
4.2.2 Learning Ruby On Rails ........................................................................................................51
4.2.3 Development and Production architecture ............................................................................52
4.3 IMPLEMENTING THE PRACTICAL APPLICATION ...........................................................53
4.3.1 A False Start with Ruby on Rails and Agile Development .....................................................53
4.3.2 Sprint 0: Compare PHP 5, PHP 5 Objects and Ruby On Rails .............................................53
4.3.3 Sprint 1: Implementing Photo Management ..........................................................................60
4.3.4 Sprint 2: Implementing Scrapbook Management...................................................................61
4.3.5 Sprint 3: Implementing the User View ...................................................................................62
4.3.6 Sprint 4: Implementing Tagging ............................................................................................63
4.3.7 Sprint 5: Implementing User Management ............................................................................64
4.3.8 Sprint 6: Adding GoogleMaps to Scrapbooks .......................................................................65
4.3.9 Sprint 7: Sorting Scrapbooks from the Web Page .................................................................66
4.3.10 Sprint 8: Providing extra Web 2.0 features ......................................................................67
5 CONCLUSIONS AND FURTHER WORK ....................................................................................68

5.1 WHAT I‟VE LEARNT ...............................................................................................................68


5.1.1 Moving to an OO MVC from a transactional environment takes time ..................................68
5.1.2 Upfront Design is still vital ....................................................................................................68
5.1.3 Consistent Ruby on Rails Documentation is lacking .............................................................69
5.1.4 Well designed Objects provide usability ................................................................................70
5.1.5 If you keep faith with RoR there will be a breakthrough! ......................................................70
5.2 WHAT I‟VE ACHIEVED/DELIVERED ...................................................................................71

5.3 WHAT I‟D DO DIFFERENTLY ................................................................................................71

5.4 AND THE ANSWER TO THE QUESTION IS…......................................................................72

5.5 FOLLOW UP .............................................................................................................................72

REFERENCES ............................................................................................................................................74

BIBLOGRAPHY.......................................................................................................................................... 77

APPENDIX A: FAMILY SCRAPBOOK APPLICATION ENTITY DIAGRAM ................................78

APPENDIX B: STORYBOARDS FOR FAMILY SCRAPBOOK APPLICATION .............................79

APPENDIX C: FAMILY SCRAPBOOK APPLICATION ROR CODE (SPRINT 1-8) ......................81

APPENDIX D: PHP & ROR COMPARISON CODE (SPRINT 0) ......................................................114


Page 6 of 126

Table of Figures
FIGURE 1: STAGES AND MAJOR LANDMARKS OF THE WEB ..........................................................10
FIGURE 2: PHP UTILISATION ON THE INTERNET, MARCH 2007 .....................................................14
FIGURE 3: VERSION DISTRIBUTION ON INTERNET WEBSITES RUNNING PHP, MARCH 2007 .14
FIGURE 4: TOP TEN MOST POPULAR PROGRAMMING LANGUAGES, AS SURVEYED BY
TIOBE SOFTWARE, AND THEIR RELATIONSHIP TO WEB DEVELOPMENT. .......................15
FIGURE 5: ANATOMY OF A WEB 1.0 APPLICATION ..........................................................................17
FIGURE 6: ANATOMY OF A WEB 2.0 APPLICATION ..........................................................................18
FIGURE 7: THE O'REILLY WEB 2.0 MEME MAP ...................................................................................20
FIGURE 8: VISUALISING WEB 2.0 (DON HINCHCLIFFE – SOCIAL COMPUTING).........................21
FIGURE 9: A WEB SYNDICATED FEED THROUGH GOOGLE READER ...........................................26
FIGURE 10: NODES IN A DOM TREE ......................................................................................................30
FIGURE 11: ANATOMY OF AN AJAX REQUESTANATOMY OF AN AJAX REQUEST ...................33
FIGURE 12: DEVELOPMENT ENVIRONMENT SELECTION CRITERIA ............................................40
FIGURE 13: UNIT TEST EXAMPLE ..........................................................................................................46
FIGURE 14: FUNCTIONAL TEST EXAMPLE ..........................................................................................47
FIGURE 15: COMPARISON OF AMOUNT OF CODE IN PHP AND ROR APPLICATION ..................59
Page 7 of 126

1 INTRODUCTION
In late 2004, a conference was organised by O‟Reilly Media and MediaLive International
[1]
. The topic of the conference was the newly coined phrase Web 2.0. This represented

the first acknowledgement of the evolution of the World Wide Web.

While many well known websites have emerged using Web 2.0 technology and

techniques (GoogleMaps, Flickr, Facebook, del.icio.us) a large number of sites are still at

the Web 1.0 stage. A key part of this dissertation is to identify some important Web 2.0

features and demonstrate how these can be implemented using one of the new framework

environments, designed for implementing these features, and how much effort is required

for a web application developer to learn one of these framework environments.

The advancement of the capabilities of web applications and the realisation of new web

techniques over the last three years has led to change in the expectations and

requirements of providers of web based business:

To the clients of web application developers, and ultimately the customers of

these clients, the needs are simply to provide an up to the minute web experience

to their customers that will involve all the buzz words and capabilities of the

moment – Web 2.0, the dynamic Web desktop, Web Services, Tagging, Mashups,

RSS, Syndication, Blogs, Wikis, Social Computing, Folksonomy.

To the web application developer the challenge is to stay ahead of the market in

technology terms while meeting the tight deadlines enforced by demanding

customers. Along with this the developer needs to provide quality software, which
Page 8 of 126

is easy to test, maintain, enhance and reuse. Does this happen now with Web 1.0

development environments? Generally not.

A recent survey shows [13] that over 50% of all websites are still produced using well

established but difficult to maintain environments using procedural type languages and

development environments.

The proliferation of new web frameworks (discussed in chapter 3) indicates that through

a move to object orientation and with the adoption of an Model-View-Controller (MVC)

design paradigm, web application developers will have the ability to meet the new

requirements of their customers to provide Web 2.0 type applications. The largest barrier

to this transformation for web application developers is the “ramp up” time, or time

required to identify, install, learn and deploy a new development environment and

produce competent, efficient and re-usable code in this environment while still managing

to meet the demanding deadlines set by customers.

Through personal experimentation, interviews with leading technology experts and

independent research this report seeks to identify the possible transition path from

tradition procedural coding such as PERL, PHP and ASP to the more modern object

oriented approach of Ruby on Rails (RoR), Django Python and CakePHP MVC

environments. These MVCs were chosen as they specifically address the needs of the

web designer and will particularly focus on the development of an application using RoR.

This dissertation intends to:

Identify and discuss, through research of academic journals, articles and papers

along with information published in books and on websites, the key


Page 9 of 126

transformations or “sea changes” of the World Wide Web. Through this research

the need for a change of development paradigm is identified for the web

application developer, this needs to be balanced against the need for fast and

responsive development and is discussed as the “web developers conundrum”.

Focus on a specific technical advancement provided by Web 2.0, the dynamic

web desktop, and provide further insight into the business case to provide this

facility.

Discuss a number of Object Oriented (OO) MVC frameworks that have emerged

to allow development of Web 2.0, specifically RoR, Django Python and Cake

PHP. As the selected framework RoR will be discussed in more detail and the

reasons why it was chosen will be explained.

Introduce and explain the practical application, Family Scrapbook, that will be

designed, tested and implemented; and the implementation of the practical

application will be used to provide an insight into the learning curve that will need

to be undertaken by web application developers to move from current technology

to Web 2.0 capable environments.


Page 10 of 126

2 THEORETICAL CONCEPTS: EVOLUTION OF THE WEB

2.1 WEB 1.0 TO 2.0 AND EVEN 3.0. THE CHANGING LANDSCAPE

As with all things in the computing world a definitive, concise and universally agreed set

of defined terms for the evolution of the World Wide Web proves difficult to identify but

the following stages have been common ground throughout my research.

Figure 1: Stages and major landmarks of the Web

2.1.1 Web 1.0

Tim Berners-Lee, 1996 [2] original vision of the World Wide Web was:

“designed originally as an interactive world of shared information through which

people could communicate with each other and with machines. Since its

inception in 1989 it has grown initially as a medium for the broadcast of read-

only material from heavily loaded corporate servers to the mass of Internet

connected consumers.”
Page 11 of 126

Web 1.0 was the original explosion of websites filled with static pages, accessed

through web browsers and using urls to send http requests. Web 1.0 went through

many iterations with the adoption of technologies such as CGI, xHTML, XML, CSS,

and JavaScript.

Within Web 1.0 the majority of the processing of information is performed on the

server-side using procedural scripting languages (PHP 4, Perl, Tcl, ASP). The server-

side architecture used in the open source community is commonly referred to as

LAMP (Linux, Apache, MySQL and Perl or PHP).

2.1.2 Web 2.0

Tim O‟Reilly, 2006 [3], whose company is credited with coining the term Web 2.0 and

is considered to be the most authoritative voice on the subject (Dion Hinchcliffe, 2007
[4]
) says:

“Web 2.0 is the business revolution in the computer industry caused by the move

to the Internet as platform, and an attempt to understand the rules for success on

that new platform. Chief among those rules is this: Build applications that

harness network effects to get better the more people use them.”

Web 2.0 is not a specific technology but a combination of technology developments

along with a change in emphasis in the deployment of websites. The primary driver of

Web 2.0 is to provide seamless applications, which gain acceptance and improvements

from user participation, without the requirement to upgrade or even know the version

of the application you are using. O‟Reilly also details a four level model of
Page 12 of 126

compliance to the ethos of Web 2.0 for Web Applications, this is detailed in chapter

2.3.4.

Ajax (Asynchronous JavaScript and XmlHttpRequest(XHR)), introduced by Garrett in

2005 [5] is one of the main components of Web 2.0, it allows the changing of the

contents of a web page without the necessity to reload the entire page or use artificial

methods of providing dynamic updating of content e.g. frames [6]. The use of the Ajax

suite of technologies moves much of the processing requirements of a web application

from the server-side to the client-side. While much of the communication between the

client-side and the server-side is expected to be XML using XHR, it will allow for

other methods like JavaScript Object Notation (JSON) and even text. Ajax should lead

to more dynamic and faster desktop applications using web technology.

Web 2.0 also introduces the idea of community information, sharing information not

just as pages served to the browser but made available by a number of methods such

as tagging and RSS through to Web Services. Further, Web Services introduces the

concept of Representational State Transfer (REST), originally defined in work by

Fielding (2000)[7]. While details of community information and web services are

beyond the scope of this research, it again indicates the importance of XML in the

future of the Web.

Ajax, Blogs, Mashups, Social Computing, Tagging, Wikis, Web Services, Web

Syndicated Feeds (RSS and ATOM) are a short glossary of terms of the key concepts

and technologies associated with Web 2.0. This is not an exhaustive list but a good

indicator of the term Web 2.0. These terms will be explored in more detail in

following sections
Page 13 of 126

2.1.3 Web 3.0

The basic idea behind the Semantic Web, also referred to as Web 3.0[8], is to make all

information on the Internet into a huge indexed database using many techniques. The

Resource Description Framework (RDF) and Web Ontology Language (OWL) are

seen by Schreiter (2007)[9] as core technology in the adoption of the Semantic Web.

In enabling Web 3.0, Web Services - using standards such as REST, SOAP and

WSDL - are key and will be provided using XML as the unifying language says

Paolucci et al (2003) [10].

The issue of providing storage, referencing and retrieval of multimedia objects is to be

addressed as part of the Semantic Web. In his talk to the W3C, Nykanen (2002) [11]

describes that:

"The Semantic Web brings to the Web the idea of having data defined and

linked in a way that it can be used for more effective discovery, automation,

integration, and reuse across various applications.”

Also, Djeraba et al [12] say:

“Semantic features involve varying degrees of semantics emphasized in

multimedia information. They make multimedia content meaningful.”

Work on the Semantic Web continues, at pace, within the research and academic

community and generates much press but no real commercial mainstream deliverables

at this point in time.


Page 14 of 126

This proposal does not seek to be comprehensive on the past and future of the web but is

merely a scene setter, for example no discussion has been included on the relative merits

of web server architectures or web portals.

2.2 WHERE ARE WE NOW?

2.2.1 What currently runs the Server-Side of the Web?

In March 2007, Nexen.net [13] identified in a survey over 10 million websites (Figure

2) that nearly 34% of websites used PHP as the server side environment, nearly 22%

used Microsoft ASP and 44% had no discernable server-side environment (likely to

use Java, PERL or have no server-side processing).

Of the PHP environments identified only 16% were PHP 5 and above, see Figure 3.

While these figures cannot be taken as definitive they do indicate that PHP is the

dominant server-side development tool in the open source web server side

environment. These figures indicate that over 50% of websites still use procedural

technologies.

Figure 2: PHP Utilisation on the Figure 3: Version Distribution on Internet


Internet, March 2007 Websites running PHP, March
2007
Page 15 of 126

2.2.2 Web Application and Systems Development Tools

In her recent article Paulson (2007) [14] identified the ten most popular programming

languages according to a survey by Tiobe Software. The list of programming

languages provided in this article has been expanded to add information on how or if

they relate to web application development, if they are a procedural or object-oriented

language, and if they are relevant to Web 1.0 or Web 2.0 and produced as Figure 4.

Figure 4: Top Ten Most Popular Programming Languages, as surveyed by


Tiobe Software, and their relationship to web development.
While it is difficult to provide complete separation between web and system

development, the first four languages generally would be termed as system

development environments. Java is used extensively in the web but often in major

systems development. C and C++ are used almost exclusively in major system

development, and Visual Basic is predominantly used in MS Windows applications.


Page 16 of 126

PHP and Perl are currently deployed in the majority of Web 1.0 server-side

applications in the LAMP environment and will use JavaScript on the client-side. In

the MS IIS environment ASP.NET will generally be deployed using VBscript (a

variant of Visual Basic).

Interestingly, three Web 2.0 style languages are in the top ten – C#, Python and Ruby,

C# is in MS ASP.NET 3.0 framework, Python is in the Django framework and Ruby

in the Rails framework. These Model-View-Controller (MVC) [15] type frameworks

have been in the object-oriented system development environment, particularly Java,

for some time but are now being designed specifically for web application

development.

While web developers are aware of alternatives to the current LAMP type web

development environment, there is concern about the ramp up time for learning and

implementing the new development environments that introduce the object-oriented

paradigm.
Page 17 of 126

2.2.3 Comparing Typical Web 1.0 and Web 2.0 Application Architecture

Figure 5: Anatomy of a Web 1.0 Application [16]

The anatomy of a typical Web 1.0 website, Figure 5, is generally described as client-side

(requests for and rendering of (x)HTML documents and request for plug in applications)

and the server-side (web server for handling requests from clients and passing on requests

to the scripting and database environment through the Common Gateway Interface

(CGI)). CGI (or similar if ASP) applications are mainly built using PHP 4, MS

ASP.NET, Perl or Java.


Page 18 of 126

Figure 6: Anatomy of a Web 2.0 Application


There are many suggestions but as yet no clear anatomy of a typical Web 2.0 website,

Figure 6 suggests an initial model and introduces the concept of the MVC framework

into the architecture.


Page 19 of 126

2.3 WEB 2.0 APPLICATIONS IN DETAIL


This section details many of the techniques used on a website that could be described as a

Web 2.0 application. A number of these techniques will be implemented in the practical

application for this dissertation.

2.3.1 A Business Layman’s view of Web 2.0


Before moving onto a detailed retrospective of Web 2.0, it is interesting to gain an

insight into the viewpoint of non-computing professionals on what Web 2.0 means to

them. In a specialist accounting publication, The CPA Journal, an article of Web 2.0

by Cong and Du [17] says:

“Web 2.0 fills the gap between a web browser and desktop applications. It brings

together documents and data scattered over local computers and the Internet, and

facilitates collaboration and sharing”.

In an interesting perspective on Web 2.0 for uses of things other than social

computing, the article points out that small firms (like the accountancy firms this

journal addresses) will gain the benefit of web based business application services that

will become available to them “because they will cost only a fraction of what

commercially designed software for large accounting firms cost”.

2.3.2 Web 2.0 – the O’Reilly Way


O‟Reilly media published their seminal and self aggrandised [18] Web 2.0 meme map

produced as part of the kick off meeting for the joint O‟Reilly and CMP conference in

October 2004 that launched Web 2.0.


Page 20 of 126

While I have struggled to identify an exact definition of what the different areas on the

Web 2.0 meme map represent, it appears to be this.

The bottom layer of flesh coloured bubbles represent a set of vague or loosely

defined concepts that people want to be reflected in Web 2.0 applications.

The middle box contains the firm concepts or mission statement for the

delivery of Web 2.0 applications.

The top layer of bubbles represents a set of vendor products or available

technologies and how they deliver Web 2.0 capabilities now.

Figure 7: The O'Reilly Web 2.0 Meme Map

The Web 2.0 meme map represents a set of ideals and capabilities required from new

web applications but does not seek to address the enabling technologies.
Page 21 of 126

2.3.3 Mapping Technology to Web 2.0 Services


The chart from Social Computing shown in figure 8, demonstrates the mapping
between Web 2.0 services (tagging, mashing etc) and the background technologies
used to create this experience.

Figure 8: Visualising Web 2.0 (Don Hinchcliffe – Social Computing)

2.3.4 Breaking the Page


The key behind all the technologies used in Web 2.0 is by breaking the principle of

Web 1.0 – the PAGE. Dave Thomas [27] says:

“(Web 2.0) is about breaking that core principle of the web (the page) and

allowing your applications to deal in smaller units of granularity, shipping data,

partial pages, and code between the browser and the server to provide a more

responsive and interactive experience”

This concept can be further expanded to include application-to-application

communications without the need for screen scraping and post processing of pages.

Through the use of web services and web syndication feeds – along with the

proprietary application programming interface (API) – content and information can be

gathered from third party sites without the need for them to be informed.
Page 22 of 126

2.3.5 Tim O’Reilly’s Hierarchy of “Web 2.0-ness”


Taken from his article on the hierarchy of web 2.0 applications [19], O‟Reilly provides
a 4 layer hierarchy of how a web application can be compared for compliance to the
ethos of Web 2.0:

Leve1 3 An application that can ONLY exist on the web. The communication between
people and application is made possible by the connectivity of the Internet. The
more people use the application, the better the application becomes because of
the added richness of content. Good examples of this are WikiPedia and
Amazon reviews.
Level 2 An application that can exist without the web but becomes much richer when
using it. The application is a useful tool to an individual but as they share the
information with others the better it becomes. Good examples are Flickr, where
an individuals set of photos gain added strength from sharing and tagging and
BaseCamp, where individuals can manage a project but gain further
productivity by sharing project information with others.
Level 1 An application exists offline but gains additional features when being online.
An example of this would be iTunes which is a music and video mangement
system but becomes a media jukebox and store when connected to the Internet.
Level 0 The application was launched as an web application but if the information was
locally cached it would still work as well. MapQuest, Yahoo! Local and
GoogleMaps are bracketed in this layer by O‟Reilly but I would argue that
GoogleMaps with it‟s extensive API and publishable maps falls into Level 2.

2.3.6 Ajax
Ajax will be covered in great detail in Chapter 2.5.

2.3.7 Blogs
Blogging is one of the earliest and most widely used forms of social computing and

the ideals of Web 2.0.

The Web Log or Blog, as it is more commonly known, is a replacement for the

personal web page where an individual created web content using html or an html

editor. Blogging sites, there are too many to mention, have replaced the need for hard

work and maintenance of a website with an easy to use web application. Many of the
Page 23 of 126

social networking sites like FaceBook, MySpace and Bebo also provide blogging

facilities.

A Blog is presented in the form of a reverse chronological list of entries and allows

readers to add comments and links to the blog. The blogging application will normally

provide an editor and allow the creation of a template to control the styles, fonts and

colours of the entries or will allow pasting from the authors favourite editor.

Most Blog applications incorporate a web syndication facility, which allows interested

parties to keep abreast of the latest entries, in the Blog without the need for regularly

visits to the blog site to look for updates.

2.3.8 Mashups
A mashup is a web application that combines it‟s own information with the

information and content source from third party websites. The third party will not be

aware of the use of it‟s content and the mashup will normally add value to the source

from which it is taking information.

In this dissertation the practical application will use the api from GoogleMaps to

overlay pin markers to a location on a map and link to photos, reviews and blogs

associated with that location. This is a mashup as it uses GoogleMaps API in a way in

which they have no control. Other sources like web services and web syndication

feeds may then be added in to the application to further extend the mashup.
Page 24 of 126

2.3.9 Tagging
Tagging is a way of associating and identifying commonality between various

disparate entities. The tagged entity could be such items a web page, a photo, a blog

entry or a book review.

Hellsten [30] says:

“Tagging, which is done by assigning a set of tags (keywords) to an entity, is used

by some of the most respected sites on the Internet. For example, Amazon, Yahoo

and Google allow their users to categorize and link together a variety of

information with the help of tags.”

Tagging will be used in the practical application for this dissertation. Photos will be

able to be tagged by the owner and individuals. Searches will then be available on a

single tag or a combination of tags to find items of similar interest.

2.3.10 Web Services

A web service is a programmatical way of accessing published and formatted

information from a remote source. A remote web service will provide a definition of

the objects available for query and the methods that can be used to access the object.

Web services can be open and freely accessible or limited by secure connections.

A number of architectures exist to provide web services but the three most popular are,

as quoted by Marshall [20]:

SOAP: Simple Object Access Protocol

REST: Representational State Transfer


Page 25 of 126

XML-RPC: Extensible Markup Language Remote Procedural Calls

SOAP is a World Wide Web consortium (w3) standard and an extension of the more

straightforward XML-RPC. Both provide programming to operations and are

supported by the further standard implementation of content delivery using XML.

SOAP further extends the content delivery through enriching the XML document to

define the service using the Web Service Description Language (WSDL). Most web

frameworks, including RoR and CakePHP provide support for web services that will

convert objects into WSDL thus making the implementation straightforward.

REST adopts CRUD (Create, Read, Update, Delete) and maps them to the standard

POST, GET, PUT, and DELETE operations available in HTML which are often

mapped onto the four basic SQL operations INSERT, SELECT, UPDATE, and

DELETE. The REST interface is more lightweight than SOAP as it is connectionless

and uses universal resource identifiers (URIs) rather than method calls.

As always, a debate rages in the computing world over the relative merits of SOAP

and REST, though in an interesting development RoR release 2.0 will enhance its

native support for REST while dropping it for SOAP and XML-RPC (they will still be

available as a plug-in).

In the practical piece of this dissertation will use the GoogleMap API web service to

gather geocoding information for the location object and plot it as a pin marker.

2.3.11 Web Syndicated Feeds (RSS and Atom)


The move from screen scraping technologies to author controlled syndicated feeds is

another main advance of Web 2.0 technology. The idea of a syndicated feed is to
Page 26 of 126

“push” out the latest updated information from a website therefore removing the need

for an individual from having to check a website for updates.

Generally a syndicated feed is implemented by a content provider, anything from the

BBC news website through to an individuals Blog, using either Really Simple

Syndication (RSS) or Atom , both of which use XML as their base

technology.

Figure 9: A Web syndicated feed through Google Reader


The receiving individual will update his/her browser or news reader application (such

as Google Reader) with the syndication feed, normally by dragging and dropping the

icons in the above paragraph. The browser or news reader then regularly checks for

changes and updates. The browser or reader will then display the headlines, see figure

9, provided in the XML document provided by the syndicated feed along with a link to

the actual article on the syndicator‟s website.


Page 27 of 126

2.3.12 Wikis
A wiki is a lightweight web database that allows contribution and editing of entries

through a standard browser. While some entries are controlled and invigilated, many

are freely available for instant contribution and updating.

The most well know Wiki (and one of the best examples of Web 2.0 in context) is

Wikipedia.

2.4 THE WEB DEVELOPERS CONUNDRUM


The move from Web 1.0 to Web 2.0 requires a transition from a mass of isolated

information, accessed through links, navigated by resource locators, and identified by

search tools to an idea of community information identified by tags, and served out by

RSS channels and the need to introduce Web Services. There is also the need to

incorporate blogging and wikis into web applications. Also, XML is the leading

facilitator of information sharing in Web 2.0 and beyond and is a cornerstone of the new

technologies.

These changes in use of the Web translate into the need for development or upgrade of

websites using an appropriate paradigm. Without these changes vast tracts of existing

websites may become obsolete due to their inability to incorporate these functions.

To ensure website development meets the requirements of Web 2.0 and Web 3.0, web

application developers need to make a decision to either:

a. Commit now to new enabling technology within their web application

development thus suffering the shorter term pain of learning new techniques and

tools
Page 28 of 126

OR

b. Wait to see how the new techniques are adopted and potentially be left behind by

their competitors in the products they can deliver to their customers.

Developers of web applications need to consider a balance between the need to deliver

new content to tight customer deadlines and looking to future proof their work with some

design considerations.

2.5 CREATING THE ACTIVE DESKTOP IN WEB 2.0


The major focus for the practical application of this dissertation will be the development

of a website, „Family Scrapbook‟, using the RoR MVC framework. The main focus of

the „Family Scrapbook‟ will be the creation and display of associated photo and location

information.

While it is intended to incorporate a number of the Web 2.0 techniques discussed in the

previous chapter, it is expected that this application will make widespread use of AJAX

to give the web pages a more dynamic, active desktop like feel.

Pages will be updated without the need for page reloads and highlighting effects will be

used to draw the users eye to updates and changes.

2.5.1 AJAX – the great misnomer


Jesse James Garrett coined the term Ajax in his 2005 article [5] it was not a new

technology but a neat term to describe the ability to create dynamic web client side

content. Garrett‟s acronym while not radical in technology terms, no new language or

development paradigm was proposed, neatly encapsulated the basic techniques and

requirements needed to bring abilities of a desktop application to web based


Page 29 of 126

applications. The term AJAX became a label and easy to comprehend concept for

dynamic web client taking the technologies out of the development labs and into the

marketplace.

Ajax stands for Asynchronous, Javascript And XmlHTTPRequest(XHR). As with

most computing terms this is a complete misnomer.

A - While AJAX can and often does act Asynchronously, allowing the user

continue working on a page while awaiting the return updates from an AJAX

request. However, AJAX can also be set to block further user activity until

updates have been processed.

J - JavaScript is not the only client side language available – Visual Basic and

Flash scripting can also be used to call the core XHR object. In fact, XMLHttp

was originally implemented as an ActiveX object and was solely used by Visual

Basic in its early days.

X - XML is not the sole communications mechanism used by the XHR object.

HTML, JavaScript Object Notation (JSON), plain text, images etc can all be

proved as responses to the XHR object.

In his book Ajax on Rails, Raymond (2007) says[21]:

“Ajax is the use of browser native technology (e.g. JavaScript and the DOM, but

not Flash) to decouple the user interaction processes from server communication

processes”

One thing the AJAX acronym does not credit (though Garrett does in his article) is the

importance of standardised XHTML and CSS adherence in the web client, namely the
Page 30 of 126

Document Object Model (DOM). The language independent DOM provides a

structure to a web page that allows traversal through a XHTML document as an object

and the elements contained within are locatable and addressable.

[22]
Figure 10: Nodes in a DOM Tree
In Figure 10, the DOM contains an element node (Invoice) which contains attribute

nodes and sub elements. This organisation is key to the effective use of AJAX. Using

the JavaScript notation document.getElementById(„Invoice‟).getAttribute(„Customer‟)

would return the string “Allison White”.


Page 31 of 126

Similarly document.getElementById(„Invoice‟).setAttribute(„City‟,‟Princeton‟) would

update the value of the Invoice.City attribute which would be updated on the web

page.

While the name AJAX is often derided as a marketing term due to its inaccuracy, the

important issue is that the Garrett‟s article was the point that married common

understanding of the needs to produce interactive, responsive web applications with

technology which enabled these requirements. This enabled development teams,

product managers and entrepreneurs to produce the elevator pitch [23] to raise funding

or management commitment to projects using AJAX type applications.

While AJAX provides a wealth of improvements in the dynamic abilities of a desktop

to a web application there are currently a number of shortcomings that need to be

addressed. These include the back button, saving links and providing rich media

content.

2.5.2 Prototype and Script.aculo.us


While it is possible to write AJAX functions using the raw XHR object (and I done this

in the first piece of work when uploading photos using AJAX and PHP), most books

recommend the use of the freely available Prototype and Script.aculo.us JavaScript

framework libraries. These libraries are bundled with RoR and most of the other web

MVC frameworks

Prototype simplifies the process of creating XHR objects, provides error handling and

browser type discovery through its AJAX object. The two main methods are

AJAX.Request, which provides an object with the response from the remote server, or

AJAX.Updater, which will actually update the webpage attribute without the need for
Page 32 of 126

further coding. I have used AJAX.Request in the GoogleMap mashup of the Family

Scrapbook application.

Script.aculo.us is built on the Prototype and provides the many screen effects used

throughout the Family Scrapbook application. Any effects seen on the web page after an

Ajax update e.g. highlighted images, blind down effects and the like are all part of the

Script.aculo.us framework. Script.aculo.us also provides the drag and drop capabilities

used to reorder the Scrapbook photos in the practical application.

2.5.3 How AJAX works


The diagram in Figure 11 gives an outline of the workings of an Ajax request.

An event on the web page (mouse click, clicking a submit button etc) will

trigger a JavaScript event.

The JavaScript event will create an AJAX request via the XHR object and send

information back to the server.

The server will handle the request and return results in XML, JSON or HTML

to the XHR object

JavaScript will then use the results in the XHR object to update the appropriate

web page XHTML elements


Page 33 of 126

Figure 11: Anatomy of an Ajax RequestAnatomy of an Ajax request [24]

This is a simplification of the process and I have suggested a number of sources of

further information on AJAX in the bibliography.


Page 34 of 126

3 SELECTING THE DEVELOPMENT ENVIRONMENT

3.1 WEB 2.0 DEVELOPMENT FRAMEWORKS

3.1.1 What is the “framework”


In this document and many other pieces of literature the word framework is used to

describe a type of development environment. A good definition of the framework is

provided by Guzewich et al [25]:

“For many software systems, a large portion of development effort is spent on

writing general purpose components that can be re-used throughout the main

application, rather than on domain-specific functionality of the application‟s

features. The collection of reusable features that is developed is commonly

referred to as the applications framework”

3.1.2 MVC – the Model-View-Controller

All the development environments under consideration support and attempt to enforce

the MVC architecture on web applications under development. The main idea of the

MVC architecture is to decouple the knowledge of a web application (the data –

usually held in a database) from the output mechanism (usually pages in a browser).

As Dave Thomas [27] said:

“When developers first started producing web applications, they went back to

writing monolithic programs that intermixed presentation, database access,

business logic, and event handling in one big ball of code”

An MVC uses the controller to handle requests coming from a requesting source,

browser or web service, interacts with the model to gain the required knowledge and
Page 35 of 126

passes the knowledge to a view which is rendered in the appropriate format to the

requesting source.

3.1.2.1 The Model

In web application models, a single model generally represents the object definition

of a database table. It enforces validations, which could include ensuring that certain

fields must be present for an object to be permanently stored by the model, enforcing

enumerators so that a file type field must conform to a limited number of options.

Also, business logic can be enforced by the model e.g. the minimum price of a

component cannot be less than £1 etc.

All calls from the controller to get information are handled by the model, so the

actual underlying database is unknown and irrelevant to the controller. This means

that a model could move from MySQL to Oracle without any changes being made to

the rest of the application, as it would be transparent to anything but the model.

3.1.2.2 The View

The view provides the output from the application and presents data provided by the

model through the controller. The view has no knowledge of the contents of the

model other than how to present it.

If a „scrapbook‟ view is passed 100 „photo‟ objects to display, it will know what size

image to show for each photo and how many photos to display per line but the actual

names and locations of the photos will be passed to the view in an object from the

controller which was populated by the model


Page 36 of 126

A view may also present a form to the end user asking for information to input but

any processing of the information put into that form will be handled by the controller

and if permanent storage of the information is required the controller will

communicate with the model.

3.1.2.3 The controller

The controller handles and validates incoming user requests from the users of the

application. If appropriate it will get from or store information in the model and ask

the view to render the appropriate response to the user.

3.1.3 Object-Oriented vs Procedural Development Paradigms

There are some basic differences between the two development paradigms.

Procedural - Based on a group of procedures that perform a function on data. The

problem is that any procedures are normally based on a workflow, a

set of functions and the data is updated when appropriate. If many

workflows intersect on the same piece of data it may get changed in

unexpected ways.

Procedural development is generally quick to produce and the code

easy to read.

Object-Oriented– creates a model of a real world object called a class, that defines

properties of the object and the methods on the way that object

behaves. This is known as encapsulation or data hiding as the object

can only be affected by its methods. Inheritance means an object

can inherit the properties and methods of another object e.g. a man
Page 37 of 126

object would inherit a human object and a father object would inherit

a man object. Finally, a class can be reused by any other application.

3.1.4 Object Oriented Code – Relevance to Web Design

Encapsulation or Data-hiding is probably the single biggest advantage for web

development using an OO paradigm. Encapsulation means that all access to data is

strictly controlled by the methods of the class that provides access to the state of the

underlying object. The methods can be updated or altered to improve logic or

performance without affecting the whole system as long as the methods still perform

the tasks stated originally. In the MVC architecture discussed earlier in this chapter,

the model handles all encapsulation.

The other major advantage is the ability to build testing throughout the lifecycle of the

application through class tests. In the terms of the three development environments

discussed in this thesis, Unit testing performs class tests. Each class can be treated

individually to ensure that the object handles all input and output without the need for

a fully working application. This means that each object can be created, maintained

and implemented as individual building blocks for the application in the knowledge

that they will react as expected when called.

3.1.5 Why objects are good


A good example of the beauty of objects is demonstrated in the photos PHP object

used in Sprint 0 of the implementation. Early in the development cycle the photo files

were stored by the object in directories representing the gallery and were retrieved

from these directories when requested. Later the photos were stored in a single
Page 38 of 126

directory and a database entry was made for each photo denoting which gallery was

the photos home gallery. These changes were made in the object but retained the same

interface/method behaviour. The page designer did not need to know any changes

occurred at the back end.

3.1.6 Potential Frameworks: CakePHP, Django, and Ruby on Rails

3.1.6.1 Django
Django is the Python Web Framework. Python is an object oriented scripting

language, which has gained a large user base. Django is a relatively new framework

introduced specifically to allow Python developers to create web based applications.

Django utilises a MVC framework and includes support for AJAX and syndication

feeds.

3.1.6.2 Ruby on Rails


RoR is a relatively new web application development environment based on object-

oriented development techniques. The market share of RoR is growing fast and claims

to be an object-oriented language (Ruby) using an MVC framework (Rails) and is

designed specifically for web development including all the functions required for

Web 2.0.

3.1.6.3 CakePHP
CakePHP is rapid development framework for PHP and is inspired by RoR (this is a

quote from the CakePHP website [26]). It provides an MVC framework for developing

web applications in PHP. CakePHP supports both PHP 4 and 5, the MVCs can be

implemented using either transactional or object-oriented code.


Page 39 of 126

3.1.7 Choosing Ruby On Rails

A major decision in this thesis was the selection of an appropriate tool to produce the

practical part of the dissertation.

3.1.7.1 PHP 5 v Ruby on Rails – a false start

In the initial proposal document for the dissertation I suggested that the practical

application would be built and compared using two different development

environments – PHP 5 and RoR. Some time was spent on designing the initial piece

of the practical application, the photo uploading facility, in PHP 5 using its object

capabilities.

Unfortunately, when I moved to produce the second part of the application, the photo

gallery in RoR, I discovered an uncomfortable truth. My attempt to compare PHP 5

with RoR was completely misguided, the framework environment was far superior

and quicker to produce product and test through. As has been detailed at length in

this dissertation one is a programming language and one is a development

framework. It is unfair to try and compare PHP 5 to RoR but that the comparison

should be between CakePHP and RoR.

3.1.7.2 Selection Process and criteria

Having narrowed the field to three possible development environments and having

had an abortive and time-consuming diversion into developing an object model with

PHP 5, I decided to create set of straightforward criteria to choose between the

CakePHP, Django and RoR.


Page 40 of 126

CakePHP Django Ruby on Rails

Open Source Yes Yes Yes

Native MySQL Support Yes Yes Yes

Runs on Mac OS X Yes No Yes

Runs on Red Hat Fedora Yes Yes Yes

Time to install 2 hours 7 hours 3 hours

Time to create database 2 hours unfinished 30 minutes

Time to create album page unfinished unfinished 5 hours

Books available (Amazon) 0 3 16

Figure 12: Development Environment Selection Criteria

The criteria used, detailed in Figure 12 were based on the ability to support an open

source environment, be available on my development and production environment,

be easy to install, quick to configure, straightforward to produce a simple page

representing items in a database and have available supporting literature (I like to

have books around to refer to rather than on line references – it is a personal choice).

Apart from time to install, RoR was equal or superior to the other two platforms.

CakePHP was quick to install, RoR provided awkward to locate and install a native

driver for MySQL 5 and Django required numerous „makes‟ to install correctly.
Page 41 of 126

3.2 RUBY ON RAILS

This section looks to provide an overview of how RoR was developed, it‟s main

advantages and introduces the RoR concept of embedded test driven development.

3.2.1 History of Ruby on Rails

Designed by David Heinemeier Hansson (commonly referred to as DHH) at the

company 37 Signals to create Basecamp, a project management system – RoR was

born. RoR was first released in July 2004.

Described by Dave Thomas [27] :

“Ruby on Rails is a MVC framework that makes it easier to develop, deploy and

maintain web applications”

Rails (the framework itself) is written in Ruby. Ruby is an object-oriented

programming language written by Yukihiro Matsumoto and first released in 1995 [28].

While it gained popularity in Japan, it was little used in Europe or the United States

until the advent of Rails. Ruby is a fully functional object-oriented programming

language that supports dynamic typing, unlike Java and C++ which are said to be

statically typed.

Dynamic typing allows more flexibility in the way the variables are assigned a type

when the program is running, an integer can become a string or more importantly a

decimal without the need for specific transformation code and will rely on validation

in the object before being stored in, for example, a database. Statically typing enforces

strict adherence to the defined variable type and unless specifically allowed within the

program will produce an error if assigned a new data type without being handled by
Page 42 of 126

the object. Dynamic typing allows for more flexibility but can cause issues at

validation or storage time or with calculations if the variable has been transformed to

an unexpected type. Static typing is said to be “type-safe” avoiding an unexpected

behaviour due to variable types during program execution time. The distinction

between dynamic and static typing is an area of heated debate in the development

community – just google “static vs dynamic types” to get a feeling for this but the

flexibility of dynamic typing is key to the success of Ruby. It allows programs to be

added to a running application without the need for recompilation and provides a

greater margin for error in the creation and storage of objects.

RoR provides all the tools necessary to create a web application except a persistent

database and a web server. It is provided through an open source license and can be

installed on all major platforms including many Linux variants, Apple Mac OS X and

MS Windows. DHH and 37 Signals are keen Mac OS X exponents and recommend

this as the development environment

While RoR does not provide an underlying database, support is provided for all the

major open source databases including MySQL, Postgres and SQLite and a number of

major commercial databases including Oracle, DB2 and SQL Server.

The RoR framework provides a small lightweight web server called WEBrick that can

be used for a development and test environment. WEBrick is not recommended as a

production web server as it is single-threaded and lacks the security, and maturity of

Apache and MS IIS.


Page 43 of 126

3.2.2 Advantages of Ruby on Rails

Guiding principals of RoR are the following (and reflect the introduction to Ajax on Rails
by Scott Raymond [29]):

Frameworks are extractions

Extracting the infrastucture of the original project, the Basecamp application, and

making it a framework created RoR. This abstraction allowed DHH to understand

the user led requirements for an MVC framework that would easily and

consistently create web applications. Each new iteration of the RoR development

environment is said to be a further extraction of work undertaken by the RoR

community, so that any new feature or library within the framework reflects a real

world need. Hence the RoR framework is said to be an extraction

Convention over configuration

When a new RoR application is created by running the command:

rails new_application_name

A complete directory structure containing all the required elements to run a new

application along with the necessary configuration and template files are created.

The app directory contains three sub directories controllers, models and views –

this is where RoR expects the files relating to these parts of the framework to

exist. The db directory contains the database schema, the configs directory

contains specific information, for example, of how to connect to the database.

All these conventions could be overridden if wished but mean that the developer

just concentrates on developing the application not building the framework. It


Page 44 of 126

may be argued that this is a restrictive environment but it really does concentrate

developers on development.

Opinionated Software

Terrible name, great concept! If you want a database table to contain photos

[plural] then you ask RoR to create a migration called photo [singular]. RoR will

create a database table called photos with a unique column called id for each row

and a model in the framework called photo. It will now understand that photos are

a number of photo objects each uniquely identified by the id field. If another

migration is created called album [singular of albums] with a field called

photo_id, and a single line is added to the photo and album model files, RoR will

automatically assume that this relates to a unique id field in the photos table.

Without any major configuration work, RoR has assumed that there is a one

[album] to many [photos] relationship – hence the name “Opinionated Software”.

As with all things, this “opinion” can be overridden in the configuration of the

model.

Don‟t Repeat Yourself

There should be only one, authoritative source for each piece of information. This

principle is common to application developers but maybe less well enforced in

web applications. Adherence to an MVC framework should aid this aim by

removing storage of data from the output source. Any view that is requested from

a web browser or a web service is rendered by the controller and updated with the

current state of the model. The rendering negates the need for pre-formatted pages
Page 45 of 126

and ensures that duplicate and potentially out of date information is not output to

users of the web application.

If a user requests to view the photos in an album, the controller will ask the model

for the current contents of the album and pass that to the view for formatting. All

the view needs to do is format the output and needs no knowledge of the previous

or current contents of the album.

3.2.3 Test Driven Development with Ruby on Rails


RoR supports the concept of test driven development (TDD). Testing is often an

afterthought in software development and has traditionally been hard to achieve in

web applications and heavily reliant on manual tests. Without testing and the ability to

perform regression testing (testing that everything that used to work before a new

feature was added still works) has been time consuming and poorly done.

TDD assumes the following four steps in each stage of development [30]:

Write a test for a specific piece of functionality

Ensure the test fails (as the code hasn‟t been written yet)

Write only the code necessary to pass the test

Refactor the code to ensure elegant and simple design

RoR provides three types of testing, unit, functional and integration that are built

round the Test class using assert methods. An assertion will either be true and a test

will pass or false and a test will fail.


Page 46 of 126

3.2.3.1 Unit Testing


A unit test is used to assess the functionality and accuracy of an object. Figure 13

shows a simple unit test that creates a new Photo object then tests that the object

responds to the correct name and method and that the value of the photoName

attribute matches the string „mates‟.

class PhotoTest < Test::Unit::TestCase


fixtures : photos
def test_new_photo
photo = Photo.create(:photoName => 'mates',
:photoTitle => 'Luke Matt and Me',
:photoType => „jpg‟
)
assert_equal 'mates', photo.photoName
end
end

Figure 13: Unit Test Example


Obviously, unit tests for a real web application will be far more complex and contain

many assertions but this illustrates the ease of testing in RoR.

3.2.3.2 Functional Testing

Functional tests are used to test complete controllers and imitate the calls made by

web browsers and web services. They are tests for use cases to ensure that a call

receives the appropriate response. Figure 14 shows two functional tests:


Page 47 of 126

class AccountControllerTest < Test::Unit::TestCase


fixtures :users
def setup
@controller = AccountController.new
@REQUEST = ACTIONCONTROLLER::TESTREQUEST.NEW
@response = ActionController::TestResponse.new
end
def test_should_login_and_redirect
post :login, :login => 'peter', :password => 'shearan'
assert session[:user]
assert_response :redirect
end
def test_should_fail_login_and_not_redirect
post :login, :login => 'quentin', :password => 'bad password'
assert_nil session[:user]
assert_response :success
end

Figure 14: Functional Test Example


The first attempts to access the login controller and post values „peter‟ as the login

name and „shearan‟ as the password. The test assertions then check that a user

session has been created and that the user is then redirected to the next page. If these

are true then the tests will pass. The second test posts incorrect user values and uses

test assertions to ensure that the user session was not created and that the user was

not redirected to the next page (the assert_response :success represents the object

handling the incorrect login correctly). These tests ensure that the login part of the

system is functioning correctly.

3.2.3.3 Integration Testing

Integration tests can span controllers and would allow a complete end to end test. For

example an integration test would combine the tests shown in Figures 13 and 14to

prove that a user called „peter‟ could successfully log onto the system and add a new

photo to the photos table.


Page 48 of 126

4 PRACTICAL APPLICATION RESEARCH PROJECT

4.1 ABOUT THE PRACTICAL APPLICATION


This section identifies the practical research project, details the basic features to be

provided in the application, explains the development and deployment environment,

provides the project design plan and includes discussion pieces on discoveries and

deliverables at various times of the project.

As this MSc is a conversion course, it is not intended that the practical application will be

a production ready, tested application. The application is support for the research project

to provide practical proof of the researched benefits of an OO MVC framework.

Any major conclusions will be provided in the final section of the document.

Database design and storyboards will be included in the appendices to this document.

4.1.1 Aims of the practical project


There are a number of reasons to attempt a practical element to this dissertation
submission.

Firstly, is the application and implementation of skills learnt on the previous units of
this MSc course in Internet and Multimedia Systems.

Secondly, is to demonstrate the ability to quickly comprehend and implement an


application in a new development environment.

Thirdly, is to demonstrate a practical implementation of a number of the features of


Web 2.0 technology in an application.

Finally, to answer the question posed as the title to the dissertation:

“Does an Object Oriented MVC paradigm get Web Application Developers on


track for Web 2.0?”
Page 49 of 126

4.1.2 Time taken to learn new development environment


A key to the decision on moving to any new development environment is the time
taken to assess, learn, install and become proficient in the new language.

It comes with extreme difficulties if the new development environment uses a new
paradigm (e.g moving to object orientation), a new platform (e.g linux to windows) or
if a current system needs to be maintained

4.1.3 Applied Research - practical application overview


A web application called “Family Scrapbook” will be the practical focus of this MSc
dissertation.

The practical element of this project will be the definition and development of a web
based application to share multimedia alongside relevant blogging and location
information between family and friends.

The applied research will investigate various parts of the anatomy of a web
application; identify the main components and processes within that part; and
demonstrate the differences between a transactional and an object-oriented approach
to the development and deployment of that part

Key parts of the anatomy that will be investigated are:

Storing and Tagging Information

Storing multimedia files (photo, video and audio).

Referencing the files from a database.

Storing blogs (blogging text) in a database.

Storing location information.

Tagging the files and blogs.

Creating and storing relationships between multimedia files, location information


and blogging text.
Page 50 of 126

Display and Outputting Information

Outputting information to a web page.

Creating mashup web pages using the GoogleMaps API.

Displaying and navigating to tagged information.

Applying constraints to information access

Controlling access to stored information.

Creating constraints beyond the traditional user/group type access

While this functionality is not unique and there are some commercial applications (e.g.
Flickr) which provide many facilities of the practical application that is irrelevant. The
practical application is an example of the use of Web 2.0 technologies implemented
using an object-oriented paradigm, demonstrating the advantages of this approach over
the transactional approach.

4.2 PREPARING TO IMPLEMENT THE PRACTICAL APPLICATION

4.2.1 Agile Development Process


The practical application will be developed using the Agile development methodology
supported by the creators of RoR. Agile is a development process built around short
development cycles (Sprints) that have a defined, prioritised customer feature list and
encourage customer interaction and comments. Agile is said to favour software over
documentation and is based on Extreme Programming (XP), which was introduced by
Kent Beck (1999) [31].
Page 51 of 126

4.2.2 Learning Ruby On Rails


At the outset of this dissertation, I have no experience of RoR and limited experience
of object oriented programming, I did a unit on this course that taught Java for one
semester. I have limited previous experience of Perl, PHP, C and COBOL
programming along with some experience of shell programming for Unix
Page 52 of 126

administration. I could not be described by any stretch of the imagination as a


programmer.

As there are limited training courses, I will attempt to learn RoR using the tutorials
and books recommended on the RoR website, http://www.rubyonrails.org/.

I intend to start the learning process in mid-June 2007 and be ready to start work on
the practical application by 1 September 2007.

4.2.3 Development and Production architecture


As detailed previously the majority of the work to be undertaken in the practical
project will be using the RoR development environment, some work will also use PHP
5 and all storage will be provided by MySQL.

The development and testing work will be done on an Apple MacBook running Mac
OS X 10.4 (Tiger). This was chosen as the development work can be undertaken at
any physical location and can act as a self contained environment.

The production system will be a x86 Intel platform running Red Hat Fedora 6, a linux
based open source operating system. This system already hosts my personal website
www.shearan.com and is accessible on the Internet.

The software platform is:


Page 53 of 126

4.3 IMPLEMENTING THE PRACTICAL APPLICATION


RoR code for the final application can be found in Appendix C.

PHP code for Sprint 0 can be found in Appendix D.

4.3.1 A False Start with Ruby on Rails and Agile Development


When attempting to build a RoR application from scratch using Agile development
techniques my knowledge of RoR was quickly exhausted and my application was
poorly designed and implemented. In early October, I sat back and reviewed my code
and realised I had made the basic error of trying to write a transactional application in
an object oriented language. At this time I was extremely concerned that after three
months of study that I was nowhere near able to implement the model I required,
mainly through lack of knowledge and understanding of RoR.

I decided at this point I needed to start again but this time I needed to understand and
document the model up front and that I needed to produce a storyboard to have a
planned user interface. The entity diagram for the database design and object models
for the Family Scrapbook application can be found in Appendix A and the storyboards
for the main web pages can be found in Appendix B.

For quite some time during the second iteration of the project I was still struggling
with the implementation of objects and understanding the syntax of RoR but at some
point in mid-October I realised that I has become very comfortable with development
environment and that the application was starting to take shape.

4.3.2 Sprint 0: Compare PHP 5, PHP 5 Objects and Ruby On Rails


The aim of this sprint is to undertake two significant tasks in both PHP 5 and RoR and
compare time and ease of development. It is intended to confirm that RoR is the
correct choice for the practical application development environment.

PHP has until the latest version been a transactional language but version 5 introduces
the ability to produce object constructs with many of the capabilities of an object-
oriented language while retaining the ability to be used as a purely transactional
language.
Page 54 of 126

PHP also features a set of libraries for manipulating image files.

These characteristics of PHP 5 provided the ideal opportunity to highlight the


differences and advantages of an object-oriented approach in “Family Scrapbook” web
application.
Page 55 of 126

Sprint 0.1: Uploading pictures with PHP 5

Main Files Include Files Library Files


testgall.php connect.php filedir.php

justfile.php photo.php

Skills Used

Allows users to browse, select images to upload and display the uploaded images. The
files are stored on the web server. The OO approach greatly reduces the workload and
complexity of the harness program called initially by the “Submit” button. The harness
creates an instance of the photo object with the details of the image file. All control of the
status, error checking and the ability to affect the image file are controlled by the object
and it‟s associated method calls. The harness program simply executes some method calls
to the object but has no need to know the state of the image file, this is in the object.

The harness makes use of PHP‟s built in image manipulation libraries. The page is
redrawn after upload as AJAX is not implemented.

Web 2.0 functionality

None.
Page 56 of 126

Sprint 0.2: Mapping Objects in PHP 5 with GoogleMaps API.

Main Files Include Files Library Files


mapit.php dbconnector.php functions.js

process_form.php locations.php

Skills Used

This example implements the GoogleMaps API, this API is an object-oriented piece of
code that extends the basic XHR object to allow the placement of information icons and
overlay routes onto a map.

This demonstrates the practical implementation of the Google Maps API to embed a
mashup within a web application. Using the information entered into the “Add a new
location” form the web page takes a number of actions”:

It validates the input fields using Javascript, generates Latitude and Longitude co-
ordinates using the API, an XHR Object is created in a Javascript function, the XHR
Object is then passed a Universal Resource Locator (URL) containing the script to be
running and a set of variables to be POSTed. The new entry is updated on the map.

Web 2.0 functionality

Mashup.
Page 57 of 126

Sprint 0.3: Uploading pictures in Ruby On Rails.

Model View Controllers


asset.rb index.rhtml asset_controller.rb

_list_item.rhtml

Skills used

Installed and used „mini-magick‟ gem to manipulate images before storage on server.

Implemented RoR AJAX helper calls to Prototype and Script.aculo.us in the


asset_controller create method. This allows newly uploaded photo to be displayed on the
page without a full refresh of the page.

Web 2.0 functionality

AJAX based dynamic desktop.


Page 58 of 126

Sprint 0.4: Mapping objects in Ruby On Rails with GoogleMaps API

Model View Controllers


scrapbook.rb index.rhtml google_controller.rb

application.js

Skills used

Integrated GoogleMaps using GoogleMaps API to create and display location attributes
in the scrapbook model.

Web 2.0 functionality

Mashups

AJAX based dynamic desktop


Page 59 of 126

Sprint 0.5: Review PHP 5 vs RoR

Sprints 0.2 through to 0.4 provide evidence that identical functionality and capabilities
are available in both PHP and RoR. The table in figure 15 confirms that the use of the
RoR MVC framework greatly reduces the amount of code that needs to be written to
achieve the same task.

PHP Ruby On Rails

Uploading Pictures 468 152

Mapping Objects 313 222

Figure 15: Comparison of amount of code in PHP and RoR application

The MVC framework of RoR adds control, structure and clarity to the development of
the example web applications. In PHP the application was a selection of randomly named
files in a directory while the RoR file location and naming conform to the conventions
required to make best use of the framework.

The experiment in Sprint 0 appears to validate the claim of RoR to reduce the complexity
and time taken to produce web applications. For this reason the remainder of the practical
application will be developed using RoR.
Page 60 of 126

4.3.3 Sprint 1: Implementing Photo Management

Model View Controllers


photo.rb /admin/index.rhtml /admin/photo_controller.rb

picture.rb /admin/_list_item.rthml pictures_controller


photopicture.rb /admin/create.rjs

Skills used

Created models with “has_many_and_belongs_to_many” properties.

Implemented uploading into the model using library „attachment_fu‟.

Installed and used „mini-magick‟ gem to manipulate images before storage in image.

Created method in picture.rb to allow rotation of the images.

Used AJAX, implemented in create.rjs, to update the list of photos with new photo
highlighted without a page refresh.

Web 2.0 functionality

AJAX based dynamic desktop.


Page 61 of 126

4.3.4 Sprint 2: Implementing Scrapbook Management

Model View Controllers


scrapbook.rb /admin/add_photos.rhtml /admin/scrapbook_controller.rb
photo.rb /admin/_list_item.rthml Pictures_controller
connection.rb /admin/add_photos_to_scrapbook.rjs
/admin/make_default_photo.rjs

Skills used

Created connection model with “join” properties between scrapbook and photo allowing
a many to many relationship between the scrapbook and photos while recording the
position and default photo attributes in the connection model.

Implemented adding to scrapbook from existing photos using collections.

Reused add photo method from the photo model and view.

Used AJAX, implemented in create.rjs, to update scrapbook when photos are added or
made default.

Web 2.0 functionality

AJAX based dynamic desktop


Page 62 of 126

4.3.5 Sprint 3: Implementing the User View

Model View Controllers


scrapbook.rb slider.rhtml library_controller.rb
photo.rb _slider.rthml
connection.rb update_slider.rjs
cart.rb main_picture.rjs

Skills used

Created slider collection of photos in scrapbook and displayed tiny images of photos in
collection as selection menu.

Update main photo from selection on slider collection or use of next and previous arrows.

Implemented shopping cart using add to cart button.

When in existence, shopping cart object and its attributes are displayed with control
buttons.

Web 2.0 functionality

AJAX based dynamic desktop


Page 63 of 126

4.3.6 Sprint 4: Implementing Tagging

Model View Controllers


photo.rb slider.rhtml library_controller.rb
tag.rb _ main_picture.rthml tag_controller.rb
update_slider.rjs /admin/photo_controller
/admin/edit.rhtml

Skills used

Implemented tagging using „acts_as_taggable‟ library.

Amended edit and update of photo model to allow tagging.

Created tag methods to locate related (tagged) photos and related (tagged photos other
tags) tags.

Display related photos and related tags on slider view.

Web 2.0 functionality

AJAX based dynamic desktop

Tagging
Page 64 of 126

4.3.7 Sprint 5: Implementing User Management

Model View Controllers


user.rb list_users.rhtml login_controller.rb
_ list_user.rthml application_controller.rb
add_user.rjs
delete_user.rjs

Skills used

Created users and login facility.

Implemented user login validation in application controller to restrict access.

Created user administration page.

Web 2.0 functionality

AJAX based dynamic desktop

NOTE: Sprint 5.3: Implement family groups based access has not yet been
implemented due to time constraints. This will be added if there is sufficient time before
the submission date. About 8 hours work is required to implement and test this facility.
Page 65 of 126

4.3.8 Sprint 6: Adding GoogleMaps to Scrapbooks

Model View Controllers


scrapbook.rb /admin/scrapbook/index.rhtml library_controller.rb
application.js google_controller.rb
/library/index.rhtml /admin/scrapbook_controller.rb

Skills used

Integrated GoogleMaps using GoogleMaps API to create and display location attributes
in the scrapbook model.

Updated home view to allow navigation to scrapbooks via icon or location on Google
map.

Web 2.0 functionality

Mashups

AJAX based dynamic desktop


Page 66 of 126

4.3.9 Sprint 7: Sorting Scrapbooks from the Web Page

Before Save

After Save

Model View Controllers


scrapbook.rb /admin/scrapbook/index.rhtml /admin/scrapbook_controller.rb

application.js

Skills used

Wrote JavaScript methods in Prototype and Script.aculo.us. This allows drag and drop
sorting on the screen. When save button is pressed the new order is saved in the model,
the default photo updated and the scrapbook redrawn using Ajax

Web 2.0 functionality

AJAX based dynamic desktop for sorting and redisplay


Page 67 of 126

4.3.10 Sprint 8: Providing extra Web 2.0 features

Model View Controllers


post.rb /posts/list.rhtml feed_controller.rb

comment.rb /posts/edit.rhtml posts_controller.rb


/feed/photos.rxml /admin/scrapbook_controller.rb

Skills used

Implemented basic blogging interface. 8 hours work required to add to scrapbooks and
test.

Created syndication feed. 8 hours more work required to implement in scrapbook


controller.

Web 2.0 functionality

Blogging

Syndication

NOTE: Sprint 8.3: Add Web Service interface has not yet been implemented due to
time constraints. This will be added if there is sufficient time before the submission date.
About 12 hours work is required to implement and test this facility.
Page 68 of 126

5 CONCLUSIONS AND FURTHER WORK

5.1 WHAT I’VE LEARNT


The overall project has been an extremely satisfying academic exercise in terms of
learning new information, gaining new skills, applying those skills and reflecting on
the initial question against this newly acquired perspective.

I feel the major lessons, in terms of a technology discussion, I have learnt are as
follows.

5.1.1 Moving to an OO MVC from a transactional environment takes time


The time taken to move past the basic RoR demonstration applications, illustrated in
books and on web based tutorials, was significant. After working through the
applications provided in the Agile Rails and Rails E-Commerce books, I was
extremely confident of producing an application in a short period of time, this proved
to be false optimism.

As detailed in section 4.3.1, I had far greater difficulty than I had anticipated
applying my basic RoR and Agile skills on the practical application. It took around
three and a half months of work for me to become confident in my abilities to write
in RoR.

5.1.2 Upfront Design is still vital


Understanding the Objects in Ruby and mapping those onto a relational database is
an extremely confusing process. It is poorly documented and everybody has his or
her own method of approaching this issue. If you mix a number of different authors
methods together things will end up working poorly and the code will have no
consistency.

Design is all, despite what you are told in numerous RoR books, blogs and websites.
Not having a well defined Entity Model that mapped onto a relationship diagram
caused untold problems and resulted in a complete rewrite from scratch of the
application. Yes, you can bolt on extra pieces to the Rails application but the core
Page 69 of 126

Objects must be designed and their key methods defined before coding starts. Sorry
DHH, you are wrong.

5.1.3 Consistent Ruby on Rails Documentation is lacking


RoR documentation is an eclectic collection of wikis, blogs, web articles and books.
During a steep learning curve on Rails my greatest frustration was a lack of a
coherent documentation source. Maybe this is a price paid for using an open-source
development framework without the support of a major company (think Java and
Sun Microsystems).

Rails claim to strength is being an „opinionated‟ framework, however clear


appropriate documentation for the „opinionated‟ pieces it contains need to be
provided. In books and web sites I have come across remarkable capabilities for
validation of objects in the model – but where is the list and explanation of all the
validations available in a model. A model is a key „opinion‟ and surely deserves to
be well documented.

Rails provides some amazing features in the framework, I just wish I knew what they
all were. People have produced some amazing work to provide plugins for Rails that
make the application environment a wonderful place, „acts_as_taggable‟,
„attachment_fu‟ and „mini-magick‟ are three that spring to mind. It does highlight the
fact that RoR is still to mature as a framework as so many seemingly core facilities
are still lacking. However all these plugins suffer from extremely limited and
disjointed documentation.

This lack of a coherent documentation resource is an extreme downfall of the RoR


project and maybe a prime example of the strengths and weaknesses of Web 2.0. In a
world where everyone has an opinion and if these opinions are available
ubiquitously, whose opinion do you trust and can the trusted source be bothered to
tell you everything they know if they are not motivated to do so?

However, I have to add a big thank you to many contributors in the RoR
community. I am pleasantly surprised and impressed by the attitude and knowledge
of the contributors to the many forums and blogs.
Page 70 of 126

5.1.4 Well designed Objects provide usability


While the initial design and implementation stage of the project took far more time
and was more difficult than expected, the benefit of the Object and MVC framework
came to the fore as the project continued.

The consistency of the MVC meant that it was a clear decision where the next part of
the application would be implemented and the access to information through the
previously prepared object methods provided fast development using already tested
components.

A good example was the implementation of a „sortable scrapbook‟ through the


scrapbook administration “add photos” page. While work was required to implement
an Ajax JavaScript method to discover the new order of the photos on the page, once
this information was known an existing method (set_connection – original designed
to set the default photo) was used to save the updated scrapbook connection
information, which holds the display order of the scrapbook, and an existing partial
„list_items‟ was used by Ajax to update the screen.

I fully expected the „sortable scrapbook‟ to be one of the hardest features to create
but the existence of object methods to do the reordering and redisplay of information
meant that the major work was the development of a Ajax JavaScript function. A
function I might add that is not available through the Prototype libraries.

5.1.5 If you keep faith with RoR there will be a breakthrough!


I am a self-professed slow learner, however when finally I get something, I really get
it. In the middle of October I suddenly realised that I was creating instance variables,
class methods and application helpers without referring to many different
information sources. After three and a half months I was finally comfortable with
complex development requirements in RoR.

I believe there are numerous reasons why it took this long to grasp the more complex
issues of RoR but I feel that the main two reasons that it took this time for me were:

1) I learn quicker through a classroom environment than through a self teach


environment.
Page 71 of 126

2) Documentation for RoRs needs to be greatly improved.

5.2 WHAT I’VE ACHIEVED/DELIVERED


This dissertation has achieved the majority of the aims laid out in this documents
introduction:

Identified and discussed, through academic research, the changes in the World
Wide Web, particularly the move from Web 1.0 to Web 2.0.

Discussed in detail the characteristics of Web 2.0 type web sites and applications.

Introduced Object Oriented Model-View-Controller frameworks and discussed one


of these, RoR, in some detail.

Implemented a practical application using newly learned skills in agile


development and RoR along with techniques learnt during the MSc course. The
application implements a number of the characteristics of a Web 2.0 web site,
particularly the use of AJAX to provide an active desktop and tagging to introduce
some of the social computing capabilities.

An electronic copy of this dissertation and access to the practical application are
available at http://www.shearan.com/dissertation/

5.3 WHAT I’D DO DIFFERENTLY


Producing a research paper, learning a completely new language and way of
development and producing a practical application was too ambitious a project for the
time available.

The research paper alone could have been a dissertation and I was forced to reduce
and heavily edit the theoretical content.

Learning a new language and development style from scratch was extremely time
consuming and the project may have benefited more if the development had been
undertaken in CakePHP or Apache Tomcat as I have a working knowledge of both
PHP and Java.
Page 72 of 126

However, I am satisfied that I have been able to achieve the majority of the goals I set
out to meet.

5.4 AND THE ANSWER TO THE QUESTION IS…


From my limited experience of designing web applications using the transactional
approach of PERL and PHP, it appears that RoR is a far superior environment for web
application development. RoR upfront organisation of a web application and the
opinionated design theory was a concern before I started to develop the application, as
I thought it may be constrictive, but it proved to be completely the opposite. By the
time I understood how to create RoR objects and get them to interact, it was clear
where each piece of code needed to be placed within the MVC and that objects really
were easily testable and reusable.

I found the experience extremely satisfying, as I was able to write a solution of far
more complexity than I expected to be possible during the project.

I am extremely pleased with the capabilities of uploading, manipulating, tagging and


displaying images through the web interface and do not believe that I would be
anywhere near this point if I had decided to progress this project in PHP5 objects.

My two major concerns are:

The time it took me to become comfortable with implementing complex methods


within the RoR development MVC

The poor standard of documentation available for RoR.

This means that web application developers will need to dedicate an extended period
of time, possibly about two months, to learn the skills required to produce a RoR
application BUT I believe that the learning curve flattens out quickly and the
developer will be able to implement Web 2.0 applications in a shorter timescale than
with the existing market dominant tools of PHP and ASP.

5.5 FOLLOW UP
There are a number of other Object Oriented MVC frameworks currently available,
including Apache Tomcat (and other Java based Web MVCs), Django and CakePHP.
Page 73 of 126

It would be an informative exercise to produce a comparative application with each of


these frameworks and provide a solid comparison between these three and RoR.
Page 74 of 126

References

1
O‟Reilly, T. (2005): What is Web 2.0: Design Patterns and Business Models for the
Next Generation of Software. Retrieved September 17, 2007 from
http://www.oreilly.com/pub/a/oreilly/tim/news/2005/09/30/what-is-web-20.html
2
Berners-Lee,T. (1999): Weaving the Web: Origins and Future of the World Wide
Web. US: Texere
3
O‟Reilly, T. (2006): Web 2.0 Compact Definition: Trying Again. Retrieved April 26,
2007 from http://radar.oreilly.com/archives/2006/12/web_20_compact.html
4
Hinchcliffe, D. (2006): Best (or Most Interesting) Web 2.0 Definitions and
Explanations. Retrieved September 16, 2007 from
http://web2.socialcomputingmagazine.com/review_of_the_years_best_web_20_
explanations.htm
5
Garrett, J J. (2005): Ajax: A new Approach to Web Applications. Retrieved 21 April,
2007 from
http://www.adaptivepath.com/publications/essays/archives/000385.php
6
Shannon, R. (2006): Frames: Good or Bad. Retrieved January 5, 2007 from
http://www.yourhtmlsource.com/frames/goodorbad.html
7
Fielding, R T. (2000): Architectural Styles and the Design of Network-based Software
Architectures, PhD thesis, UC Irvine. Retrieved 22 April, 2007 from
http://www.ics.uci.edu/~fielding/pubs/dissertation/top.htm
8
Metz, C (2007): Web 3.0 Semantic Web. PC Magazine March, 2007. Retrieved March
23, 2007 from http://www.pcmag.com/article2/0,1895,2102852,00.asp
9
Schreiter, T. (2007): An Introduction into Semantic Web Services. University of
Potsdam, Germany.
10
Paolucci, M., Sycara, K. and Kawamura, T. (2003): Delivering Semantic Web
Services. Tech. report CMU-RI-TR-02-32, Robotics Institute, Carnegie Mellon
University, May, 2003
11
Nykanen, O. (2002): W3C FI & W3C Semantic Web. Retrieved March 12, 2007 from
http://www.w3c.tut.fi/talks/2003/0331umedia-on/slide6-0.html
Page 75 of 126

12
Djeraba, C., Sebe, N., and Lew, M.S. (2005): Systems and architectures for
multimedia information retrieval. Multimedia Systems Volume 6, Number 10,
October 2005, Pages 457-463.
13
Seguy, D. (2007): PHP Statistics For March 2007. Nexen.net. Retrieved April 24,
2007 from http://www.nexen.net/chiffres_cles/phpversion/16811-
php_statistics_for_march_2007.php
14
Paulson, D P. (2007). Developers Shift to Dynamic Programming Languages.
Computer Volume 40, Number 2, February 2007, Pages 12-15.
15
Anon. (2007): Model-view-controller. Retrieved 20 April, 2007 from
http://en.wikipedia.org/wiki/Model-view-controller
16
Macaulay, M. (2007): PHP with MySQL Coursenotes, Web Database for Multimedia,
LSBU, Week 6, Semester 2 2006/7.
17
Cong, Y. and Du, H. (2007): Welcome to the World of Web 2.0. The CPA Journal,
May 2007, Pages 6-10.
18
Musser, J. with O‟Reilly, T. (2006): Web 2.0 Principles and Best Practices. US:
O‟Reilly Media Inc.
19
O‟Reilly, T. (2006): Levels of the Game: The Hierarchy of Web 2.0 Applications.
Retrieved September 17, 2007 from
http://radar.oreilly.com/archives/2006/07/levels_of_the_game.html
20
Marshall, K. (2006): Web Services on Rails. US: O‟Reilly Media Inc.
21
Raymond, S. (2007): Ajax on Rails. US: O‟Reilly Media Inc.
22
Anon, LightReading.com (2002): DOM – Document Object Model Retrieved
September 25, 2007 from http://www.lightreading.com/techenc.asp?term=DOM
23
Anon(2007): Elevator Pitch. Retrieved October 24, 2007 form
http://en.wikipedia.org/wiki/Elevator_pitch
24
Batchelder, M. (2006): Ajax; Templating; and the Separation of Layout Logic.
Retrieved October 24, 2007 from http://borkweb.com/story/tag/diagram
25
Guzewich, T., Kent, M., Pfieffer, A., and Shank, K. (2006): Software Architecture
Case Study: Ruby on Rails.
26
Anon. (2007): Introduction to CakePHP. Retrieved September 25, 2007 from
http://manual.cakephp.org/chapter/intro
27
Thomas, D. and Heinemeier-Hansson, D. (2007): Agile Web Development with Rails.
US: Pragmatic Bookshelf.
Page 76 of 126

28
Anon. (2004): About Ruby. Retrieved from September 7, 2007 from http://www.ruby-
lang.org/en/about/
29
Raymond, S. (2007). Ajax on Rails. US: O‟Reilly Media, Inc.
30
Hellsten, C. and Laine, J. (2006): Beginning Ruby on Rails E-Commerce, From
Novice to Professional. New York: Apress.
31
Beck, K. (1999) Extreme Programming Explained: Embrace Change. New York,
Addison-Wesley.

Biblography
Babin, L. (2007): Beginning Ajax with PHP, From Novice to Professional. New York:
Apress.
Page 77 of 126

Welling, L and Thomson, L. (2005): PHP and MySQL Web Development, 3rd Edition.
Indiana: Developer‟s Library.

Fehily, C. (2002): Visual Quickstart Guide, Python. Berkeley, Peachpit Press.

Holzner, S. (2007): Ajax Bible. Indianapolis: Wiley Publishing.

MØller, A. and Schwartzbach (2006): An Introduction to XML and Web Technologies.


Harlow, Essex: Addison Wesley.

Darie, C., Brinzarae, B., Bucica, M. and Chereches-Tosa, F. (2006): Ajax and PHP,
Building Responsive Web Applications. Birmingham, UK: Packt Publishing.

Thomas, D. (2006): Programming Ruby, the Pragmatic Programmers‟ Guide. US:


Pragmatic Bookshelf.

Fowler, C. (2006). Rails Recipes. US: Pragmatic Bookshelf.

Lewis, A., Purvis, M., Sambells, J and Turner, C.(2007): Google Maps Applications with
Rails and Ajax. From Novice to Professional. New York: Apress.
Page 78 of 126

APPENDIX A: Family Scrapbook Application Entity Diagram

Com m ents User s 1 1


S cr apbook
1
id :int 1 id :int
id :int
* post_id :int 1
name :string
name :string

body :string 1 hashed_password :string


1 user_id :int
user_id :int salt :string
create date :datetime

lat :dec (15,10)

lng :dec (15,10)


P osts icon :text(100)
1
id :int

title :string Connections Tags


body :text 1
id :int id :int
created_at :datetime
* scrapbook_id :int name :string
scrapbook_id :int * photo_id :int created :datetime

1
user_id :int * default_photo :T/F

position :int P hotos_tags

* photo_id :int

P hotos tag_id :int


1 1
P ictur es
1
id :int *
id :int
1 name :string
filename :string
desc :string
content_type :string
owner_id :int
size :int
date :datetime
width :int
aspect :string
height :int

parent_id :int K ey
P hotos - pictur es
thumbnail

createdat
:string

:datetime
* photo_id :int
1

1 1
* One to many relationships

One to one relationships


picture_id :int
*
Page 79 of 126

APPENDIX B: Storyboards for Family Scrapbook Application

Slider Page

ScrapbookT itl e
Scrapbook T itle Shows no. of times
Logo Vis iting Info visited this page
illustrating successful
use of ajax

Thumbnails

M ENU R elated Photos

Toggles to show
tiny related photos

M ain Photo
R elated Tags
SHOPPING
Ajax updates Tags
Visible if exisit s
CART
Description
Add to shopping cart

Clicking thumbnail or related photos


or tags creates an Ajax transition of main photo

Scrapbook view

ScrapbookT itl e
Scrapbook T itle
Logo Vis iting Info
Q u ic k T im e ™ a n d a
G r a p hic s d e c o m p r e s s o r
a r e e e d
n e d t o s e e t h i
s p i
c t u r e .

Q u ic k T im e ™ a n d a
G r a p hic s d e c o m p r e s s o r
a r e e e d
n e d t o s e e t h i
s p i
c t u r e .

Q u ic k Tim e ™ a n d a
G r a p hic s d e c o m p r e s s o r
a r e e e d e
n d t o s e e t h is p ic t u r e .

Icons of Default
Photo for scrapbook
Nam e Nam e Nam e Nam e Nam e Nam e

M ENU Mashup from Google


Na m e
Maps
De s c r ip t io n

L in k

Pinhead locations for


Scrapboook

Naviga
t e t o sc
r apbookvia pinhea
d or icon
Page 80 of 126

Add Photos to Scrapbook

Add photos toŅScrapbookT itlÓ


e
Add photos toŅScrapbookT itlÓ
e
Logo Vis iting Info

Current photos in
scrapbook

D efault Photo
Edit buttons:-
Rotate Left / Right
M ENU Make Default
Delete photo
save

Add existing photos to


scrapbook

Upload photos to
scrapbook

Save new photo order after sorting on screen


Page 81 of 126

APPENDIX C: Family Scrapbook Application RoR Code (Sprint 1 – 8)


Models
***** CART.RB *****
class Cart
attr_reader :items

def initialize
@items = []
end

def add_hardCopy(photo)
current_item = @items.find{|item| item.photo == photo }
if current_item
current_item.increment_quantity
else
current_item = CartItem.new(photo)
@items << current_item
end
current_item
end

def remove_product(photo)
current_item = @items.find {|item| item.photo == photo}
current_item.decrement_quantity
if current_item.quantity == 0
@items.delete(current_item)
end
current_item
end

def total_items
@items.sum { |item| item.quantity}
end

def total_quantity
@items.sum{ |item| item.quantity }
end
end

***** cart_item.rb *****


class CartItem

attr_reader :photo, :quantity

def initialize(photo)
@photo = photo
@quantity = 1
end

def increment_quantity
@quantity += 1
end
Page 82 of 126

def decrement_quantity
@quantity -= 1
end

def name
@photo.name
end

def title
@photo.description
end

def size
@photo.origSize
end

def thumb
thumb = photo.pictures.first.public_filename(:thumb)
end
end

***** comment.rb *****


class Comment < ActiveRecord::Base
belongs_to :post
end

***** connection.rb *****


class Connection < ActiveRecord::Base
belongs_to :scrapbook
acts_as_list :scope => :scrapbook_id
belongs_to :photo
end

***** photo.rb *****


class Photo < ActiveRecord::Base

has_and_belongs_to_many :pictures
has_many :connections
has_many :scrapbooks, :through => :connections

acts_as_taggable
validates_presence_of :name

def name_desc
"#{name} #{description}"
end

def add_photo_with_pictures(params)
picture = Picture.create(params[:picture])
photo = Photo.new(params[:photo])
photo.name = File.basename(picture.filename,".*")
photo.date = Time.now
piccies = Picture.find_by_sql("select * from pictures where filename like '#{photo.name}%'")
photo.owner_id = piccies.size
photo.save
Page 83 of 126

piccies.each do |piccie|
photo.pictures << piccie
end
photo
end
end

***** picture.rb *****


class Picture < ActiveRecord::Base

has_and_belongs_to_many :photos
has_attachment :storage => :file_system,
:max_size => 4.megabytes,
:thumbnails => { :large => '600x600', :medium => '480x480>',
:thumb => '100x100!', :tiny => '40x40>' },
:processor => :MiniMagick
validates_as_attachment

def rotate_image(picture,direction)
types = ["","large","medium","thumb","tiny"]
if direction == 'left'
degrees = "-90"
else
degrees = "90"
end
for type in types do
image = MiniMagick::Image.from_file("#{RAILS_ROOT}/public"+picture.public_filename(type))
image.rotate(degrees)
image.write("#{RAILS_ROOT}/public"+picture.public_filename(type))
end
end
end

***** post *****


class Post < ActiveRecord::Base
has_many :comments
belongs_to :scrapbook
validates_presence_of :title, :body
end

***** scrapbook *****


class Scrapbook < ActiveRecord::Base
has_many :connections, :order => :position
has_many :posts
has_many :photos, :through => :connections, :order => :position
has_many :default_photos, :through => :connections, :source => :photo, :conditions => ['default_photo =
1']

validates_presence_of :name

def self.find_not_in_scrapbook(target_scrapbook)
in_scrapbook = target_scrapbook.photos
not_in_scrapbook = Array.new
all_photos = Photo.find(:all)
not_in_scrapbook = all_photos - in_scrapbook
Page 84 of 126

not_in_scrapbook
end

def self.find_all_except_default
find_by_sql("select * from scrapbooks where name not like 'default%'")
end

def self.find_with_marker
find_by_sql("select * from scrapbooks where lat is NOT NULL and lng is NOT NULL")
end
end

***** tag.rb *****


class Tag < ActiveRecord::Base
has_and_belongs_to_many :photos
end

***** user.rb *****


require 'digest/sha1'

class User < ActiveRecord::Base


validates_presence_of :name
validates_uniqueness_of :name

attr_accessor :password_confirmation
validates_confirmation_of :password

def validate
errors.add_to_base("Missing Password") if hashed_password.blank?
end

def after_destroy
if User.count.zero?
raise "You can't delete the last user!!!"
end
end

def self.authenticate(name, password)


user = self.find_by_name(name)
if user
expected_password = encrypted_password(password, user.salt)
if user.hashed_password != expected_password
user = nil
end
end
user
end

def password
@password
end

def password=(pwd)
@password=pwd
return if pwd.blank?
Page 85 of 126

create_new_salt
self.hashed_password = User.encrypted_password(self.password, self.salt)
end

private

def self.encrypted_password(password, salt)


string_to_hash = password + "wibble" + salt # wibble just makes it harder
Digest::SHA1.hexdigest(string_to_hash)
end

def create_new_salt
self.salt = self.object_id.to_s + rand.to_s
end
end

VIEWS
***** /admin/photo/_form.rhtml *****
<table>
<tr>
<td>
<dl>
<dt>ID</dt><dd><%= @photo.id%></dd>
<dt>Name</dt><dd><%= @photo.name %></dd>
<dt>Description</dt><dd><%= @photo.description%></dd>
<dt>Owner ID</dt><dd><%= @photo.owner_id%></dd>
<dt>Date</dt><dd><%= @photo.date%></dd>
<dt>Aspect</dt><dd><%= @photo.aspect%></dd>
<dt>Tags</dt><dd><%= display_tags_in_link @photo%></dd>
</dl>
</td>
<td>
<%= image_tag(@photo.pictures.first.public_filename(:medium)) %>
</td>
</tr>
</table>

<% if @photo.tags.size > 0 %>


<div id="recommended">
<h2>Recommendations</h2>
<h4>Photos</h4>
<% for photo in @photo.tagged_related %>
<%= link_to image_tag(photo.pictures.first.public_filename(:tiny)), :action => 'show', :id
=> photo %>
<%= link_to photo.name, :action => 'show', :id => photo %><br />
<% end %>

<h4>Tags</h4>
<% for tag in Photo.find_related_tags(@photo.tags.collect(&:name), :separator => ',', :raw =>
true, :limit => 100)%>
<%= link_to tag['name'], :controller => '/tag', :action => 'show', :id => tag['name']%><br
/>
<% end %>
</div>
<% end %>
Page 86 of 126

<p><%= link_to 'Edit', :action => 'edit', :id => @photo %> | <%= link_to 'Back', :action => 'index' %></p>

***** /admin/photo/_list_item.rhtml *****


<div id="picture_<%= list_item.id %>" class="thumb">
<table id="picture">
<tr><td colspan="4">
<% for picture in list_item.pictures %>
<% if !picture.parent_id? %>
<%= link_to image_tag(picture.public_filename(:thumb)), { :action =>
'show', :id => list_item } %>
<% end %>
<% end%>
</td></tr>
<tr><td>
<%= form_remote_tag :url => {:action => :spin_left, :id => list_item } %>
<%= image_submit_tag("/images/rotleft.jpg")%>
<%= end_form_tag %>
</td>
<td>
<%= link_image_to "/images/edit.jpg",{:action => 'edit', :id => list_item } %>
</td>
<td>
<%= form_remote_tag :url => {:action => 'destroy', :id => list_item },
:confirm => "Do you really want to delete #{list_item.name}"%>
<%= image_submit_tag("/images/del.jpg")%>
<%= end_form_tag %>
</td>
<td>
<%= form_remote_tag :url => {:action => :spin_right, :id => list_item } %>
<%= image_submit_tag("/images/rotright.jpg")%>
<%= end_form_tag %>
</td></tr></table>
</div>

***** /admin/photo/_photo.rhtml *****


<tr>
<td><%= photo.id%></td>
<td><%= link_to photo.name, :action => 'show', :id => photo %></td>
<td><%= photo.description%></td>
<td><%= photo.owner_id%></td>
<td><%= photo.date%></td>
<td><%= photo.aspect%></td>
<td><%= link_to 'Edit', :action => 'edit', :id => photo %></td>
<td><%= button_to "Delete",{ :action => 'destroy', :id => photo},
:confirm => "Do you really want to delete #{photo.name}"%></td>
</tr>

***** /admin/photo/create.rjs *****


page.insert_html :bottom, "photos", :partial => 'list_item', :object => @new_photo
page.visual_effect :highlight, "picture_#{@new_photo.id}"
page.alert("Cmmooonnnn: #{@new_photo.name}")

***** /admin/photo/destroy.rjs *****


page["picture_#{@photo.id}"].visual_effect :blind_up
Page 87 of 126

***** /admin/photo/edit.rhtml *****


<div id="main_picture">
<table>
<tr><td>
<%= image_tag(@photo.pictures.first.public_filename(:medium)) %>
</td><td>
<% form_tag :action => 'update',:id => @photo do %>
<%= render(:partial => 'form') %>
<%= submit_tag 'Edit' %>
<% end %>
</td></tr>
</table>
</div>

<%= link_to 'Show', {:action => 'show', :id => @photo.id }%>|
<%= link_to 'Back', {:action => 'index'} %>

***** /admin/photo/index.rhtml *****


div id="photos">
<% @photos.each do |photo| %>
<%= render(:partial => 'list_item', :object => photo) %>
<% end %>
</div>

<!--Ajaxified Form - Replaces New -->


<% form_for(:scrapbook, :url => {:action => 'create'},
:html => { :multipart => true, :target => 'upload_frame', :id=> 'add_photo' }) do
|form| %>
<p>
<label for="uploaded_data">Upload a file:</label>
<%= form.file_field "uploaded_data", :name => 'picture[uploaded_data]' %></p>
<p>
<label for="Description">Description:</label>
<%= form.text_field "description", :name => 'photo[description]'%></p>
<p>
<label for="Scrapbook Name">Allocate to scrapbook:</label>
<select id="scrapbook_id" name="scrapbook">
<% for scrapbook in @scrapbooks %>
<option value="<%= scrapbook.id %>"
<% if scrapbook.id == 12 %>
selected="selected">
<% else %>
>
<% end %>
<%= scrapbook.name %></option>
<% end%>
</select>
<%= submit_tag "Create" %>

</p>
<% end %>
<iframe id='upload_frame' name="upload_frame" style="width:1px;height:1px;border:0px"
src="about:blank"></iframe>
Page 88 of 126

<!--Make the divs sortable with Ajax-->


<%= sortable_element :photos, :tag => 'div'%>
<%= if @photo_pages.current.previous
link_to("Previous page", { :page => @photo_pages.current.previous })
end
%>
<% if @photo_pages.current.previous && @photo_pages.current.next %>
|
<% end %>
<%= if @photo_pages.current.next
link_to("Next page", { :page => @photo_pages.current.next })
end
%>
</p>

***** /admin/photo/show.rhtml *****


<table>
<tr>
<td>
<dl>
<dt>ID</dt><dd><%= @photo.id%></dd>
<dt>Name</dt><dd><%= @photo.name %></dd>
<dt>Description</dt><dd><%= @photo.description%></dd>
<dt>Owner ID</dt><dd><%= @photo.owner_id%></dd>
<dt>Date</dt><dd><%= @photo.date%></dd>
<dt>Aspect</dt><dd><%= @photo.aspect%></dd>
<dt>Tags</dt><dd><%= display_tags_in_link @photo%></dd>
</dl>
</td>
<td>
<%= image_tag(@photo.pictures.first.public_filename(:medium)) %>
</td>
</tr>
</table>

<% if @photo.tags.size > 0 %>


<div id="recommended">
<h2>Recommendations</h2>
<h4>Photos</h4>
<% for photo in @photo.tagged_related %>
<%= link_to image_tag(photo.pictures.first.public_filename(:tiny)), :action => 'show', :id
=> photo %>
<%= link_to photo.name, :action => 'show', :id => photo %><br />
<% end %>

<h4>Tags</h4>
<% for tag in Photo.find_related_tags(@photo.tags.collect(&:name), :separator => ',', :raw =>
true, :limit => 100)%>
<%= link_to tag['name'], :controller => '/tag', :action => 'show', :id => tag['name']%><br
/>
<% end %>
</div>
<% end %>
Page 89 of 126

<p><%= link_to 'Edit', :action => 'edit', :id => @photo %> | <%= link_to 'Back', :action => 'index' %></p>

***** /admin/scrapbook/_addable_photos.rhtml *****


<h2>Photos to Add?</h2>
<% form_remote_for :scrapbook, :url => {:action => 'add_photos_to_scrapbook', :id => @scrapbook.id},
:html => {:id => "add_scrapbook"} do |form| %>
<% for photo in @not_in_scrapbook%>
<input type="checkbox" name="photo_ids[]" value="<%= photo.id %>" />
<%= image_tag(photo.pictures.first.public_filename(:tiny)) %>
<% end %>
<%= submit_tag "Add Photos" %>
<% end %>

***** /admin/scrapbook/_current_photo.rhtml *****


% if @scrapbook.default_photos.size > 0
default_photo = @scrapbook.default_photos
else
default_photo = [0]
end
%>
<% if default_photo[0] == current_photo %>
<div id="photo_<%= current_photo.id %>" class="default_thumb">
<table id="picture">
<tr><td><%=
image_tag(current_photo.pictures.first.public_filename(:thumb)) %></td></tr>
<tr><td>Default</td></tr></table></div>
<% else %>
<div id="photo_<%= current_photo.id %>" class="thumb">
<table class="picture">
<tr><td colspan="5"><%=
image_tag(current_photo.pictures.first.public_filename(:thumb)) %></td></tr>
<tr><td>
<%= form_remote_tag :url => {:action => :spin_left, :id =>
current_photo,
:scrapbook =>
"#{@scrapbook.id}" } %>
<%=
image_submit_tag("/images/rotleft.jpg")%>
<%= end_form_tag %>
</td>
<td>
<%= form_remote_tag :url => {:action =>
'make_default_photo', :id => current_photo,

:scrapbook => "#{@scrapbook.id}" }%>


<%=
image_submit_tag("/images/tick.jpg")%>
<%= end_form_tag %>
</td>
<td>
<%= form_remote_tag :url => {:action =>
'destroy_connection', :id => current_photo,
:scrapbook =>
"#{@scrapbook.id}" },
Page 90 of 126

:confirm => "Do you really want to


delete the #{current_photo.name} photo?"%>
<%=
image_submit_tag("/images/del.jpg")%>
<%= end_form_tag %>
</td>
<td>
<%= form_remote_tag :url => {:action => :spin_right, :id =>
current_photo,
:scrapbook =>
"#{@scrapbook.id}" } %>
<%=
image_submit_tag("/images/rotright.jpg")%>
<%= end_form_tag %>
</td>
</tr></table></div>

<% end %>

***** /admin/scrapbook/_list_item.rhtml *****


<div id="scrapbook_<%= list_item.id %>" class="thumb">
<table class="picture">
<tr><td colspan="2">
<% if !list_item.default_photos.empty? %>
<%=
link_to(image_tag(list_item.default_photos.first.pictures.first.public_filename(:thumb)),
{:action =>'add_photos', :id => list_item})
%>
<% else %>
<%= link_to(image_tag('/images/quest.jpg'),{:action =>'add_photos', :id =>
list_item}) %>
<% end %>
</td></tr>
<tr><td>
<%= link_to list_item.name, :action => 'add_photos', :id => list_item %>
</td>
<td>
<%= form_remote_tag :url => {:action => 'destroy', :id => list_item },
:confirm => "Do you really want to delete the #{list_item.name}
scrapbook?"%>
<%= image_submit_tag("/images/del.jpg")%>
<%= end_form_tag %>
</td></tr>
</table>
</div>

***** /admin/scrapbook/_scrapbook_sortable.rhtml *****


<div id="scrapbook_sortable" class="scrapbook_sortable" scrapbook="<%= @scrapbook.id%>">
<h2>Current Photos</h2>
<% for current_photo in @scrapbook.photos%>
<%= render :partial => 'current_photo', :object => current_photo %>
<% end %>
</div>
<%= sortable_element :scrapbook_sortable, :tag => 'div', :url => {:action => 'sortem'}%>
Page 91 of 126

***** /admin/scrapbook/add_photos.rhtml *****


<%= render :partial => 'scrapbook_sortable'%>
<input type="button" onClick="updateScrapbookOrder()" value="Save updated scrapbook order"/>
<div id="addable_photos">
<%= render :partial => 'addable_photos'%>
</div>

<!--Ajaxified Form - Replaces New -->


<h2>Photos to Upload?</h2>
<% form_for(:scrapbook, :url => {:action => 'create'},
:html => { :multipart => true, :target => 'upload_frame', :id=> 'add_photo' }) do
|form| %>
<p>
<label for="uploaded_data">Upload a file:</label>
<%= form.file_field "uploaded_data", :name => 'picture[uploaded_data]' %></p>
<p>
<label for="Description">Description:</label>
<%= form.text_field "description", :name => 'photo[description]'%></p>
<p>
<label for="Scrapbook Name">Allocate to scrapbook:</label>
<select name="scrapbook">
<option value="<%= @scrapbook.id %>" selected="selected"><%= @scrapbook.name
%></option>
</select>
<%= submit_tag "Create" %>

</p>
<% end %>
<iframe id='upload_frame' name="upload_frame" style="width:1px;height:1px;border:0px"
src="about:blank"></iframe>

***** /admin/scrapbook/add_photos_to_scrapbook.rjs *****


for photo in @updated_photos
page.insert_html :bottom, "scrapbook_sortable", :partial => 'current_photo', :object => photo
page.visual_effect :highlight, "photo_#{photo.id}"
end
page.replace_html("addable_photos", :partial => 'addable_photos', :object => @not_in_scrapbook)

page.form.reset "add_scrapbook"
***** /admin/scrapbook/create.rjs *****
page.insert_html :bottom, "scrapbooks", :partial => 'list_item', :object => @scrapbook
page.visual_effect :highlight, "scrapbook_#{@scrapbook.id}"
page.form.reset "add_scrapbook"

***** /admin/scrapbook/destroy_connection.rjs *****


page["photo_#{@photo.id}"].visual_effect :blind_up
page.replace_html("addable_photos", :partial => 'addable_photos',
:object => @not_in_scrapbook)

***** /admin/scrapbook/index.rhtml *****


div id="scrapbooks">
<% if @scrapbook_pages.current.previous %>
<div class="thumb">
<%= link_to("Previous page", { :page => @scrapbook_pages.current.previous})
%>
Page 92 of 126

</div>
<%end %>
<% @scrapbooks.each do |scrapbook| %>
<%= render(:partial => 'list_item', :object => scrapbook) %>
<% end %>
<% if @scrapbook_pages.current.next %>
<div class="thumb">
<%= link_to("Next page", { :page => @scrapbook_pages.current.next }) %>
</div>
<%end %>
</div>
<div class="depot-peterform">
<fieldset>
<legend>Create a new scrapbook</legend>
<div id="map" class="mediummap"></div>
</fieldset>
</div>

***** /admin/scrapbook/resort_scrapbook.rjs *****


page['scrapbook_sortable'].replace_html :partial => 'scrapbook_sortable'

***** /feed/photos.rxml
xml.instruct!
xml.rss "version" => "2.0", "xmlns:dc" => "http://purl.org/dc/elements/1.1" do
xml.channel do
xml.title 'Family Scrapbook'
xml.link url_for(:only_path => false, :controller => 'library', :action => 'index' )
xml.pubDate CGI.rfc1123_date(@photos.first.date)
xml.description h("New Photos from the Family Scrapbook")
@photos.each do |photo|
xml.item do
xml.title photo.name
xml.link url_for(:only_path => false, :controller => 'library', :action => 'show_photo', :id => photo )
xml.description h(photo.description)
xml.pubDate CGI.rfc1123_date(photo.date)
xml.guid url_for(:only_path => false, :controller => 'library', :action => 'show_photo', :id => photo )
xml.image image_tag(photo.pictures.first.public_filename(:tiny))
end
end
end
end

***** /layouts/application.rhtml *****


<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>

<title><%= @page_title || "Family Scrapbook" %></title>


<script src="http://maps.google.com/maps?file=api&amp;v=2&amp;key=ABQIAAAAWBs-
8BZ4TjrRayH7tC7eahRSDJ5FbnUnSozdd4sY_1qb9zLM_xSU4k8noXKwWKCmq1xvX0xsZWTh6g"
type="text/javascript"></script>
<%= stylesheet_link_tag "scrapbook", :media => "all" %>
<%= javascript_include_tag :defaults %>
Page 93 of 126

</head>

<body id="screen">

<div id="banner" align="right">


<%= @page_title || "Family Travel"%>

<table id="counter" align="right">


<tr><td>Page last fully loaded:&nbsp;</td></tr>
<tr><td><%= @current_time%></td></tr>
<tr><td><% if session[:counter] %>
You've been here <%= pluralize(session[:counter], "time")%>
<% end%></td></tr>
</table>
</div>
<div id="store">

<div id="columns">

<div id="admin_user">
<% if @user %>
<h4>user id : <%= @user.name %></h4>
<%= render(:partial => "library/admin_menu")%>
<% else %>
<%= render(:partial => "library/user_menu")%>
<% end %>
</div>
<div id="shop">
<%= hidden_div_if(@cart.items.empty?, :id => "cart") %>
<%= render(:partial => "library/cart", :object =>
@cart)%></div>
</div>

</div>
<div id="main">
<%if flash[:notice] -%>
<div id="notice"><%= flash[:notice ]%></div>
<% end -%>
<%= yield :layout %>
</div>
</div>

</body>
</html

CONTROLLERS
***** /admin/photo_controller.rb *****
class Admin::PhotoController < ApplicationController
before_filter :authorize

def new
@photo = Photo.new
@page_title = 'Create new photo'
end
Page 94 of 126

def create_ajax
responds_to_parent do
render :update do |page|
page << "alert($('stuff').innerHTML)"
page.visual_effect :highlight, "stuff"
end
end
end

def create
begin
photo = Photo.new
@new_photo = photo.add_photo_with_pictures(params)
@new_photo.tag(params[:tags], :separator => ',')
scrapbook = Scrapbook.find(params[:scrapbook])
@new_photo.scrapbooks << scrapbook

rescue
redirect_to_index("Failed to create the picture - do it properly")
else
responds_to_parent do
render :update do |page|
page.insert_html :bottom, "photos", :partial => 'list_item', :object => @new_photo
page.visual_effect :highlight, "picture_#{@new_photo.id}"
page.form.reset "add_photo"
end
end
end
end

def edit
set_up
@photo = Photo.find(params[:id])
@page_title = 'Edit Photo Information'
end

def update
@photo = Photo.find(params[:id])
@photo.tag(params[:tags], :separator => ',', :clear => true)
if @photo.update_attributes(params[:photo])
flash[:notice] = "Photo #{@photo.description} was successfully updated"
redirect_to :action => 'index'
else
@page_title = 'Edit Photo Information'
render :action => 'edit'
end
end

def destroy
@photo = Photo.find(params[:id])
for connection in @photo.connections
connection.destroy
end
@photo.destroy
Page 95 of 126

end

def show
set_up
@photo = Photo.find(params[:id])
@page_title = @photo.name
end

def index
set_up
@photo_pages, @photos = paginate :photos, :per_page => 18
# @photos = Photo.find(:all)
@page_title = "Listing All Photos"
@scrapbooks = Scrapbook.find(:all, :order => 'name')
end

def get_time
sleep 1.second
render :text => Time.now.strftime("%d-%m-%Y %H:%M:%S")
end

def spin_left
photo = Photo.find(params[:id])
spin_photo(photo,'left')
end

def spin_right
photo = Photo.find(params[:id])
spin_photo(photo,'right')
end

def spin_photo(photo,direction)
picture = photo.pictures.first
picture.rotate_image(picture,direction)
render :update do |page|
page["picture_#{photo.id}"].replace :partial => "list_item", :object => photo
page["picture_#{photo.id}"].visual_effect :highlight
end

end

private

def redirect_to_index(msg = nil)


flash[:notice] = msg if msg
redirect_to :action => :index
end
end

***** /library/_admin_menu.rhtml *****


<a href="/library/">Home</a><br/>
<a href="/posts">Blog</a><br />
<a href="/about/">About</a><br/>
<a href="/admin/scrapbook/">Manage Scrapbooks</a><br/>
<a href="/admin/photo/">Manage Photos</a><br/>
Page 96 of 126

<a href="/login/list_users">Manage Users</a><br/>


<a href="/tag/list">Manage Tags</a><br/>
<a href="/login/logout">Logout</a><br/>

***** /library/_cart.rhtml *****


<div class="cart-title">Request to print photos:</div>
<table>
<%= render(:partial => "library/cart_item", :collection => cart.items) %>
<tr class="total-line">
<td>Total</td>
<td class="total-cell"><%= number_to_currency(cart.total_quantity)%></td>
<td>&nbsp;</td>
</tr>
<tr><td>
<% form_tag :url => { :controller => '/library' ,:action => :checkout} do %>
<%= image_submit_tag("/images/checkout.jpg")%>
<% end %>
</td>
<td>
<% form_remote_tag :url => { :controller => '/library' ,:action => :empty_cart }
do %>
<%= image_submit_tag("/images/emptycart.jpg")%>
<% end %>
</td></tr>
</table>

***** /library/_cart_item.rhtml *****


<% if cart_item == @current_item %>
<tr id="current_item">
<% else %>
<tr>
<% end %>
<td><%= cart_item.quantity %> &times; <%= h(cart_item.name)%></td>
<td class="item-price"><%= number_to_currency(cart_item.quantity)%></td>
<td>
<% form_remote_tag :url => { :controller => '/library' ,:action => :remove_from_cart, :id
=> cart_item.photo } do %>
<%= image_submit_tag("/images/del.jpg")%>
<% end %>
</td>
</tr>

***** /library/_list_item.rhtml *****


<% if !list_item.default_photos.empty? %>
<div class="thumb">
<table id="picture">
<tr><td><%=
link_to(image_tag(list_item.default_photos.first.pictures.first.public_filename(:thumb)),
:action => 'show', :id => list_item )
%></td></tr>
<tr><td><%= list_item.name %></td></tr>
</table>
</div>
<% end %>
Page 97 of 126

***** /library/_main_picture.rhtml *****


<%
is_next = false
prev = false
n_photie = false
for photo in @scrapbook.photos
if is_next
n_photie = photo
is_next = false
end
if main_picture == photo
is_next = true
p_photie = prev
end
prev = photo
end
%>

<table><tr>
<% if p_photie %>
<td>
<%= form_remote_tag :url => {:action =>'update_slider', :id => p_photie, :scrapbook =>
@scrapbook } %>
<%= image_submit_tag("/images/bwdarrow.jpg") %>
<%= end_form_tag %>
</td>
<% else %>
<td><img src="/images/space.jpg" /></td>
<% end %>
<td class="mainpic" align="center">
<%= image_tag(main_picture.pictures.first.public_filename(:medium)) %>
</td>

<% if n_photie %>


<td>
<%= form_remote_tag :url => {:action =>'update_slider', :id => n_photie, :scrapbook =>
@scrapbook } %>
<%= image_submit_tag("/images/fwdarrow.jpg") %>
<%= end_form_tag %>
</td>
<% else %>
<td>&nbsp;</td>
<% end %>
<td id="recommended">
<% if main_picture.tags.size > 0 %>

<h3>Related Photos</h3>
<% if main_picture.tagged_related.size == 0%>
No Similarly tagged photos
<% end%>
<% for photo in main_picture.tagged_related %>
<%= form_remote_tag :url => {:action =>'update_slider', :id => photo,
:scrapbook => @scrapbook } %>
<%= image_submit_tag(photo.pictures.first.public_filename(:tiny)) %>
<%= end_form_tag %>
Page 98 of 126

<% end %>

<h3>Related Tags</h3>
<% for tag in Photo.find_related_tags(main_picture.tags.collect(&:name), :separator =>
',', :raw => true, :limit => 100)%>
<%= link_to_remote tag['name'], :url => {:action => 'toggle', :tag_name =>
tag['name']}%><br />
<div id="related_<%= tag['name']%>" style="display:none">
<% @tagged_list = Photo.find_tagged_with(:any => tag['name'], :separator =>
',') %>
<% for photo in @tagged_list %>
<%= form_remote_tag :url => {:action =>'update_slider', :id => photo,
:scrapbook => @scrapbook } %>
<%=
image_submit_tag(photo.pictures.first.public_filename(:tiny)) %>
<%= end_form_tag %>
<% end %>
</div>
<% end %>

<% end %>


</td>
</tr>
<tr>
<td>&nbsp;</td>
<td align="center"><%= main_picture.description %></td>
<td><%= form_remote_tag :url => {:action => :add_to_cart, :id => main_picture} %>
<%= image_submit_tag("/images/cart.jpg")%>
<%= end_form_tag %>
</td>
</tr>
</table>

***** /library/_slider.rhtml *****


<table id="slidertab"><tr>
<% for photo in @scrapbook.photos %>
<td>
<%= form_remote_tag :url => {:action =>'update_slider', :id => photo,
:scrapbook => @scrapbook } %>
<%= image_submit_tag(photo.pictures.first.public_filename(:tiny)) %>
<%= end_form_tag %>
</td>
<% end %>
</tr></table>

***** /library/_user_menu.rhtml *****


<a href="/library/">Home</a><br />
<a href="/posts">Blog</a><br />
<a href="/about/">About</a><br/>
<a href="/login/login">Login</a><br />

***** /library/add_to_cart.rjs *****


page.select("div#notice").each {|div| div.hide }
page.replace_html("cart", :partial => "cart", :object => @cart)
page[:cart].visual_effect :blind_down if @cart.total_quantity == 1
Page 99 of 126

page[:current_item].visual_effect :highlight, :startcolor => "#C0C0C0",:endcolor => "#114411"

***** /library/index.rhtml *****


<div id="scrapbooks">
<% @scrapbooks.each do |scrapbook| %>
<%= render(:partial => 'list_item', :object => scrapbook) %>
<% end %>
</div>

<div id="map" class="mediummap"><div id="map2"></div></div>

***** /library/show.rhtml *****


<div id="library_show">
<% for photo in @scrapbook.photos %>
<div class="thumb">
<%= link_to image_tag(photo.pictures.first.public_filename(:thumb)),
{ :action => 'slider', :id => photo, :scrapbook => @scrapbook } %>
</div>
<% end %>
</div>
<%= link_to 'Back', :action => 'index' %></p>

***** /library/slider.rhtml *****


<div id="slider_page">
<div id="slider">
<%= render :partial => 'slider' %>
</div>
<div id="main_picture">
<%= render :partial => 'main_picture', :object => @photo, :locals => {:photo_size =>
':medium'} %>
</div>

</div>

***** /library/toggle.rjs *****


page["related_#{@toggle}"].toggle

***** /library/update_slider.rjs *****


page.replace_html("main_picture", :partial => "main_picture", :object => @photo)

***** /admin/scrapbook_controller.rb *****


class Admin::ScrapbookController < ApplicationController
before_filter :authorize

def index
set_up
@scrapbook_pages, @scrapbooks = paginate :scrapbooks, :per_page => 4
@page_title = "Listing All Scrapbooks"
end

def create
begin
photo = Photo.new
@new_photo = photo.add_photo_with_pictures(params)
@scrapbook = Scrapbook.find(params[:scrapbook])
Page 100 of 126

@new_photo.scrapbooks << @scrapbook


rescue
redirect_to_index("Failed to create the picture - do it properly")
else
responds_to_parent do
render :update do |page|
page.insert_html :bottom, "scrapbook_sortable", :partial => 'current_photo', :object =>
@new_photo
page.visual_effect :highlight, "photo_#{@new_photo.id}"
page.form.reset "add_photo"
end
end
end
end

def destroy
@scrapbook= Scrapbook.find(params[:id])
for connection in @scrapbook.connections
connection.destroy
end
@scrapbook.destroy
end

def add_photos
set_up
@scrapbook = Scrapbook.find(params[:id])
@not_in_scrapbook =Scrapbook.find_not_in_scrapbook(@scrapbook)
@page_title = "Add photos to #{@scrapbook.name} Scrapbook"
end

def add_photos_to_scrapbook
@scrapbook = Scrapbook.find(params[:id])
@updated_photos = Array.new
for photo_id in params[:photo_ids]
@photo = Photo.find(photo_id)
@scrapbook.photos << @photo
@updated_photos << @photo
end
@not_in_scrapbook =Scrapbook.find_not_in_scrapbook(@scrapbook)
end

def destroy_connection
@photo = Photo.find(params[:id])
@scrapbook = Scrapbook.find(params[:scrapbook])
conn = Connection.find_by_scrapbook_id_and_photo_id(@scrapbook,@photo)
conn.destroy
@not_in_scrapbook =Scrapbook.find_not_in_scrapbook(@scrapbook)
end

def make_default_photo
@new_default_photo = Photo.find(params[:id])
@scrapbook = Scrapbook.find(params[:scrapbook])
if @scrapbook.default_photos.size > 0
for photo in @scrapbook.default_photos do
set_connection(@scrapbook,photo,false)
Page 101 of 126

end
end
set_connection(@scrapbook,@new_default_photo,true)
@scrapbook.reload
@not_in_scrapbook =Scrapbook.find_not_in_scrapbook(@scrapbook)
redirect_to :action => 'add_photos', :id => scrapbook.id unless request.xhr?
end

def spin_left
@scrapbook = Scrapbook.find(params[:scrapbook])
photo = Photo.find(params[:id])
spin_photo(photo,'left')
end

def spin_right
@scrapbook = Scrapbook.find(params[:scrapbook])
photo = Photo.find(params[:id])
spin_photo(photo,'right')
end

def spin_photo(photo,direction)
picture = photo.pictures.first
picture.rotate_image(picture,direction)
render :update do |page|
page["photo_#{photo.id}"].replace :partial => "current_photo", :object => photo
page["photo_#{photo.id}"].visual_effect :highlight
end
end

def resort_scrapbook
@scrapbook = Scrapbook.find(params[:scrapbook])
@scrapbook_sortable = params[:scrapbook_sortable]
for photo_id in @scrapbook_sortable.reverse
photo = Photo.find(photo_id)
conn = Connection.find_by_scrapbook_id_and_photo_id(@scrapbook,photo)
conn.move_to_top
end
default_photo = Photo.find( @scrapbook_sortable[0])
if @scrapbook.default_photos.size > 0
for photo in @scrapbook.default_photos do
set_connection(@scrapbook,photo,false)
end
end
set_connection(@scrapbook,default_photo,true)
@scrapbook.reload
end

def set_connection(scrapbook,photo,state)
conn = Connection.find_by_scrapbook_id_and_photo_id(scrapbook,photo)
conn.default_photo = state
conn.move_to_top if state
conn.save
end
end
Page 102 of 126

***** application.rb *****


class ApplicationController < ActionController::Base
# Pick a unique cookie name to distinguish our session data from others'
session :session_key => '_scrapbook_session_id'
def set_up()
@current_time = Time.now.strftime("%d-%m-%Y %H:%M:%S")
@cart = find_cart
if session[:user_id]
@user = User.find(session[:user_id])
end
@tags = Tag.find(:all)
end

private

def authorize
unless User.find_by_id(session[:user_id])
session[:original_uri] = request.request_uri
flash[:notice] = "Please Log In"
redirect_to(:controller => "/login", :action => "login")
end
end

def find_cart
session[:cart] ||= Cart.new
end
end

***** feed_controller.rb *****


class FeedController < ApplicationController
def photos
set_up
@photos = Photo.find(:all, :order => "date", :limit => 10)
@headers["Content-Type"] = "application/rss+xml"
end
end

***** google_controller.rb *****


class GoogleController < ApplicationController
def index
set_up
@page_title = "Locations on Google Maps"
end

def create
begin
scrapbook = Scrapbook.create(params[:s])
scrapbook.create_date = Time.now
if scrapbook.save
res = {:success => true, :content =>
"<div><strong>Name: </strong>#{scrapbook.name}</div><div><strong>Description:
</strong>#{scrapbook.description}</div>", :icon=>scrapbook.icon}
else
res = {:success => false,:content => "Could not save the marker"}
end
Page 103 of 126

rescue
res = {:success => false,:content => "Went pear shaped"}
render :text => res.to_json
else
render :text => res.to_json
end
end

def list
render :text => Scrapbook.find_with_marker.to_json
end
end

***** library_controller.rb *****


class LibraryController < ApplicationController

def index
set_up
@scrapbooks = Scrapbook.find_all_except_default
@count = increment_count
end

def show
set_up
@scrapbook = Scrapbook.find(params[:id])
@page_title = "#{@scrapbook.name} Scrapbook"
end

def slider
set_up
@scrapbook = Scrapbook.find(params[:scrapbook])
@photo = Photo.find(params[:id])
@page_title = "#{@scrapbook.name} Scrapbook"
end

def update_slider
begin
@photo = Photo.find(params[:id])
@scrapbook = Scrapbook.find(params[:scrapbook])
rescue
logger.error("Attempt to access invalid photo #{params[:id]}")
redirect_to_index("Invalid Photo, ID passed: #{params[:id]}")
else
redirect_to_index unless request.xhr?
end
end

def checkout
set_up
end

def toggle
@toggle = params[:tag_name]
end
Page 104 of 126

def add_to_cart
begin
photo = Photo.find(params[:id])
rescue ActiveRecord::RecordNotFound
logger.error("Attempt to access invalid photo #{params[:id]}")
redirect_to_index("Invalid Photo, ID passed: #{params[:id]}")
else
@cart = find_cart
@current_item = @cart.add_hardCopy(photo)
redirect_to_index unless request.xhr?
end
end

def remove_from_cart
begin
@cart = find_cart
photo = Photo.find(params[:id])
rescue ActiveRecord::RecordNotFound
logger.error("Attempt to access invalid photo #{params[:id]}")
redirect_to_index("Invalid product" )
else
@current_item = @cart.remove_product(photo)
redirect_to_index unless request.xhr?
end
end

def empty_cart
session[:cart] = nil
redirect_to_index unless request.xhr?
end

private

def find_cart
session[:cart] ||= Cart.new
end

def redirect_to_index(msg = nil)


flash[:notice] = msg if msg
redirect_to :action => :index
end

def increment_count
session[:counter] ||= 0
session[:counter] += 1
end
end

***** login_controller.rb *****


class LoginController < ApplicationController
before_filter :authorize, :except => :login

def add_users
set_up
@user = User.new(params[:user])
Page 105 of 126

@user.save
@all_users = User.find(:all)
end

def login
set_up
session[:user_id] = nil
if request.post?
user = User.authenticate(params[:name], params[:password])
if user
session[:user_id] = user.id
uri = session[:original_uri]
session[:original_uri] = nil
redirect_to (uri || {:controller => 'library', :action => 'index'})
else
flash[:notice] = "Invalid username/password combination"
end
end
end

def logout
session[:user_id] = nil
flash[:notice] = "Logged Out"
redirect_to :controller => '/library', :action => 'index'
end

def index
set_up
@total_photos = Photo.count
@total_scrapbooks = Scrapbook.count
end

def delete_users
if request.post?
user = User.find(params[:id])
begin
user.destroy
rescue Exception => e
flash[:notice] = e.message
end
@all_users = User.find(:all)
end
end

def list_users
set_up
@page_title = "Listing All Users"
@all_users = User.find(:all)
end
end

***** pictures_controller.rb *****


class PicturesController < ApplicationController
# GET /pictures
# GET /pictures.xml
Page 106 of 126

def index
@pictures = Picture.find(:all)

respond_to do |format|
format.html # index.rhtml
format.xml { render :xml => @pictures.to_xml }
end
end

# GET /pictures/1
# GET /pictures/1.xml
def show
@picture = Picture.find(params[:id])

respond_to do |format|
format.html # show.rhtml
format.xml { render :xml => @picture.to_xml }
end
end

# GET /pictures/new
def new
@picture = Picture.new
end

# GET /pictures/1;edit
def edit
@picture = Picture.find(params[:id])
end

# POST /pictures
# POST /pictures.xml
def create
@picture = Picture.new(params[:picture])

respond_to do |format|
if @picture.save
flash[:notice] = 'Picture was successfully created.'
format.html { redirect_to picture_url(@picture) }
format.xml { head :created, :location => picture_url(@picture) }
else
format.html { render :action => "new" }
format.xml { render :xml => @picture.errors.to_xml }
end
end
end

# PUT /pictures/1
# PUT /pictures/1.xml
def update
@picture = Picture.find(params[:id])

respond_to do |format|
if @picture.update_attributes(params[:picture])
flash[:notice] = 'Picture was successfully updated.'
Page 107 of 126

format.html { redirect_to picture_url(@picture) }


format.xml { head :ok }
else
format.html { render :action => "edit" }
format.xml { render :xml => @picture.errors.to_xml }
end
end
end

# DELETE /pictures/1
# DELETE /pictures/1.xml
def destroy
@picture = Picture.find(params[:id])
@picture.destroy

respond_to do |format|
format.html { redirect_to pictures_url }
format.xml { head :ok }
end
end
end

***** posts_controller.rb *****


class PostsController < ApplicationController
def index
list
render :action => 'list'
end

# GETs should be safe (see http://www.w3.org/2001/tag/doc/whenToUseGet.html)


verify :method => :post, :only => [ :destroy, :create, :update ],
:redirect_to => { :action => :list }

def list
set_up
@posts = Post.find :all
end

def show
@post = Post.find(params[:id])
end

def new
@post = Post.new
end

def create
@post = Post.new(params[:post])
if @post.save
flash[:notice] = 'Post was successfully created.'
redirect_to :action => 'list'
else
render :action => 'new'
end
end
Page 108 of 126

def edit
@post = Post.find(params[:id])
end

def update
set_up
@post = Post.find(params[:id])
if @post.update_attributes(params[:post])
flash[:notice] = 'Post was successfully updated.'
redirect_to :action => 'show', :id => @post
else
render :action => 'edit'
end
end

def comments
set_up
@post = Post.find(params[:id])
if @post.comments.create(:body => params[:comment])
flash[:notice] = 'Comment was successfully added.'
else
flash[:notice] = 'Failed to add Comment.'
end
redirect_to :action => 'show', :id => @post
end

def destroy
Post.find(params[:id]).destroy
redirect_to :action => 'list'
end
end

***** tag_controller.rb *****


class TagController < ApplicationController

def list
set_up
@page_title = 'Listing tags'
@tag_pages, @tags = paginate :tags, :order => :name, :per_page => 15
end

def show
set_up
tag = params[:id]
@page_title = "Photos tagged with #{tag}"
@photos = Photo.find_tagged_with(:any => tag, :separator => ',')
end
end

MISCELLANEOUS
***** /public/javascripts/application.js *****
var centerLatitude = 51.465471;
var centerLongitude = -0.143320;
Page 109 of 126

var startZoom = 13;


var map;
var map2

function init() {
if (document.getElementById("map")) {
drawMap();
}
}

function drawMap()
{
map2 = document.getElementById("map2");

if (GBrowserIsCompatible()) {
if (map2) {
startZoom = 2;
} else {
startZoom =13;
}
map = new GMap2(document.getElementById("map"));
listMarkers();
map.addControl(new GMapTypeControl());
map.addControl(new GSmallMapControl());
map.setCenter(new GLatLng(centerLatitude, centerLongitude), startZoom);

if (!map2) {
GEvent.addListener(map,"click",function(overlay,latlng){
var inputForm = document.createElement("form");
inputForm.id='geocache-input';
inputForm.setAttribute("action","");
inputForm.onsubmit = function() {createMarkerInPrototype(); return false;};

var lng = latlng.lng();


var lat = latlng.lat();

inputForm.innerHTML = '<fieldset style="width:150px;"'


+'<legend>New Scrapbook V14</legend>'
+'<label for="name">Name</label>'
+'<input type="text" id="name" name="s[name]" style="width:100%;"/>'
+'<label for="description">Description</label>'
+'<input type="text" id="description"
name="s[description]"style="width:100%;"/>'
+'<input type="submit" value="create" />'
+'<input type="hidden" id="longitude" name="s[lng]" value="'+lng+'" />'
+'<input type="hidden" id="latitude" name="s[lat]" value="'+lat+'"/>'

map.openInfoWindow(latlng,inputForm);
});
}
}
}
Page 110 of 126

function createMarkerInPrototype() {
var lng = $("longitude").value;
var lat = $("latitude").value;
var formValues=Form.serialize('geocache-input');
new Ajax.Request('/google/create',
{
method: 'post',
parameters: formValues,
onComplete: function(request) {
res=eval( "(" + request.responseText + ")" );
if(!res.success) {
alert(res.content);
} else {
var latlng = new GLatLng(parseFloat(lat),parseFloat(lng));
var marker = addMarkerToMap(latlng, res.content, res.icon);
map.addOverlay(marker);
map.closeInfoWindow();
}
}
});
}

function addMarkerToMap(latlng, html, iconImage )


{
if (iconImage != ''){
var icon = new GIcon();
icon.image = iconImage;
icon.iconSize = new GSize(25,25);
icon.iconAnchor = new GPoint(14,25);
icon.infoWindowAnchor = new GPoint(14,14);
var marker = new GMarker(latlng,icon);

} else {
alert("didn't get in");
var marker = new GMarker(latlng);
}
GEvent.addListener(marker, 'click', function() {
var markerHTML = html;
marker.openInfoWindowHtml(markerHTML)
});
return marker;
}

function listMarkers ()
{
new Ajax.Request('/google/list',
{
method: 'post',
onComplete: function(request) {
markers=eval( "(" + request.responseText + ")" );
for (var i=0; i < markers.length; i++ ) {
var marker=markers[i].attributes;
Page 111 of 126

var lat=marker.lat;
var lng=marker.lng;
if (lat && lng) {
var latlng = new GLatLng(parseFloat(lat),parseFloat(lng));
var html = '<div><b>Name:</b> '
+ marker.name
+ '</div><div><b>Description:</b> '
+ marker.description
+ '</div><div><a href="/library/show/'
+marker.id
+'">Open Scrapbook</a></div>';
var iconImage = marker.icon;
var marker = addMarkerToMap(latlng, html, iconImage );
map.addOverlay(marker);
}
}
}
});
}

function updateScrapbookOrder()
{
var fred = Sortable.serialize("scrapbook_sortable");
var scrapbook =
document.getElementById("scrapbook_sortable").getAttribute('scrapbook');
var sendit = "scrapbook="+scrapbook+"&"+fred;
new Ajax.Request('/admin/scrapbook/resort_scrapbook',
{
method: 'post',
asynchronous:true,
evalScripts:true,
parameters: sendit
});
}

function addMarker(latitude,longtitude,description)
{
var marker = new GMarker(new GLatLng(latitude,longtitude));
GEvent.addListener(marker, 'click',
function() {
marker.openInfoWindowHtml(description)
}
);
map.addOverlay(marker)
}

function getGroupOrder() {
var sections = document.getElementsByClassName('scrapbook_sortable');
var alerttext = '';
sections.each(function(section) {
var sectionID = section.id;
var order = Sortable.serialize(sectionID);
alerttext += sectionID + ': ' + Sortable.sequence(section) + '\n';
});
alert(alerttext);
Page 112 of 126

return false;
}

window.onload=init;
window.unload=GUnload;

APPENDIX D: PHP and RoR Comparison Code (Sprint 0)


***** testgall.php *****
<html>
<head>
</head>
<body>
<?php
include("connect.php");
require_once("justfile.php");
if (!$_POST['submit'])
{
?>
Page 113 of 126

<form enctype="multipart/form-data" action="<?=$_SERVER['PHP_SELF']?>"method="post">


<b>Gallery Name (files go in default if no gallery provided):</b>
<br>
<input type="Text" name="gallery" size="40">
<p>
File:
<br>
<input type="file" name="file">
<p>
<input type="submit" name="submit" value="Add Image">
</form>

<?php
}
else
{
// validate form data
// Create the file object
$uploadFile = new JustFile($_FILES['file']);

// Top yourself if the object isn't move than 0 bytes


if (!$uploadFile->fileSize()) { die("<b><p>Bad upload!</b></p>"); }

// Set up temp array in case the response is the bad file type array
$joe = $uploadFile->mimeToWord();

// If unknown then top yourself otherwise print out the filetype


if ($joe[0] == "Unknown")
{
die("<p><b>Not a photo filetype. It is a [$joe[1]]</b></p>");
} else {
echo "<p>The file is a [<b>".$uploadFile->mimeToWord()."</b>]</p>";
}

// Display the size of the file


echo "<p>The file size is [<b>".$uploadFile->fileSize()."</b>] bytes</p>";

// Move the photo to a new location - a gallery


// The gallery is the name provided or "default"

$uploadFile->moveToGallery($_POST['gallery']);

// Create the tiny, thumb and web versions of the original photo
$uploadFile->makeWebTypes();

// Display the details of the file that is converted


$uploadFile->show();

// Display tiny,thumb, web and orig files in doc


$tinyFile = $uploadFile->getFileName('tiny');
$thumbFile = $uploadFile->getFileName('thumb');
$webFile = $uploadFile->getFileName('web');
$origFile = $uploadFile->getFileName('orig');

?>
Page 114 of 126

<img src="<?php echo $tinyFile ?>">Tiny File</>


<img src="<?php echo $thumbFile ?>">Thumb File</>
<img src="<?php echo $webFile ?>">Web File</>
<img src="<?php echo $origFile ?>">Original File</>
<?php
//
// OBJECTIFY!!!
// open connection to database
// Done via including connect.php --- going to be put in object

// formulate and execute query


$query = "INSERT INTO gallery (dsc) VALUES ('" . $_POST['desc']. "')";
$result = mysql_query($query) or die("Error in query: " . mysql_error());
$id = mysql_insert_id($connection);

//The $ext is done in the object in mimeToWord...this will not work now
$newFileName = $id . $ext;

// copy file to new location


copy($_FILES['file']['tmp_name'], "/mydatadir/" . $newFileName);

// update database with new file name


$query = "UPDATE gallery SET filename = '$newFileName' WHERE id = '$id'";
$result = mysql_query($query) or die("Error in query: " . mysql_error());
}
?>
</body>
</html>

***** connect.php *****


<?php
@mysql_connect("localhost", "peter", "blahblah") or die("<h1>PS-DB-1: Unable to connect to
database</h1>");
@mysql_select_db("webdbcw1") or die("<h1>PS-DB-2: Unable to select database</h1>");
?>
***** justfile.php *****
<?php
require_once('filedir.php');
require_once("photo.php");
class JustFile
{
public $file = array();
public $tmpFile;
public $realFile;
public $ext;
public $moved = false;
public $destination = false;

function __construct($file=false)
{
if ($file) {
$this->file = $file;
$this->tmpFile = $file['tmp_name'];
$this->realFile = $file['name'];
Page 115 of 126

} else {
echo "<p><b>PS-OB-JF-Co: No file name </b></p>";
}
$this->fileSize();
$this->mimeToWord();
}

function fileSize()
{
$size = filesize($this->tmpFile);
if ($this->file['size'] == 0) {
echo "<p><b>PS-OB-JF-Fs: Bad file size [$this->file['size']] bytes</b></p>";
return false;
break;
} else {
return $size;
}
}

function mimeToWord()
{
switch ($this->file['type']){
case "image/jpeg":
$tested = "JPEG";
$this->ext = ".jpg";
break;
case "image/pjpeg":
$tested = "JPEG";
$this->ext = ".jpg";
break;
case "image/gif":
$tested = "GIF";
$this->ext = ".gif";
break;
default:
$tested = array("Unknown",$this->file['type']);
}
return $tested;
}

function moveToGallery($galleryName) {
$gallery = new FileDir($galleryName);
if (!$gallery->dirExists()) {
// echo "<p><b>PS-OB-JF-Mtg1: Needs to create the gallery directory</b></p>";
$newDir = $gallery->createDir();
$moveIt = $this->moveFile($gallery);
if ($newDir && $moveIt) {
$this->moved = true;
// echo "<p><b>PS-OB-JF-Mtg2: Created the gallery directory and
moved the file</b></p>";
return true;
break;
}
} else {
// echo "<p><b>PS-OB-JF-Mtg3: Gallery directory already exists</b></p>";
Page 116 of 126

$moveIt = $this->moveFile($gallery);
if ($moveIt) {
$this->moved = true;
return true;
// echo "<p><b>PS-OB-JF-Mtg4: Moved the file</b></p>";
break;
}
}
echo "<p><b>PS-OB-JF-Mtg5: FAILED to create gallery directory OR move the
file</b></p>";
return false;
}

function moveFile($gallery) {
$this->destination = $gallery->fileDir.'/'.$this->realFile;
// echo "<p><b>This is destination [$this->destination]</b></p>";
if (move_uploaded_file($this->tmpFile, $this->destination)) {
return true;
break;
}
return false;
}

function makeWebTypes()
{
if ($this->moved)
{
$this->photo = new Photo($this->destination);
$this->photo->createWebPics();
} else {
echo "<p><b>PS-OB-JF-Mwp1: File needs to be moved first</b></p>";
}
return false;
}

function getFileName($type) {
if ($this->moved)
{
$getFile = $this->photo->newFileName($type);
return $getFile;
} else {
return "<p><b>PS-OB-JF-Nfn1: File needs to be moved first</b></p>";
}
}

function show() {
if ($this->moved) {
$this->photo->show();
}

}
}
?>
Page 117 of 126

***** filedir.php *****


<?php
// Written by Peter Shearan 12/8/7
// This class will be constructed with either the directory name supplied on instantiation
// or will use "default" as the base directory.
//
// Methods are then provided for creating, listing, destroying and other information provision
// about the object
//
class FileDir
{
public $fileDir;

function __construct($fileDir)
{
if ($fileDir == "")
{
$this->fileDir = "default";
} else {
$this->fileDir = $fileDir;
}
}

function dirExists($gotOne = false)


{
if (!$gotOne)
{
$dirFind = $this->fileDir;
} else {
$dirFind = $gotOne;
}

if(is_dir($dirFind)) {
// echo "<p><b>PS-OB-FD-De1: Directory $dirFind exists</b></p>";
return true;
break;
} else {
// echo "<p><b>PS-OB-FD-De2: Directory $dirFind does not exist</b></p>";
}
return false;
}

function dirPermiss($dirFind)
{
// display directory permissions
return false;
}

function dirList()
{
// list directory contents - TBC
return false;
}
Page 118 of 126

function createDir($dirName = false,$isGallery = true)


{
if (!$dirName)
{
$newDir = $this->fileDir;
} else {
$newDir = $dirName;
}

$folder = explode( DIRECTORY_SEPARATOR , $newDir );


$mkfolder = '';
for( $i=0 ; isset( $folder[$i] ) ; $i++ )
{
$mkfolder .= $folder[$i];
if (!is_dir( $mkfolder ))
{
mkdir($newDir, 0777);
if ($isGallery) {
mkdir($newDir.'/tiny');
mkdir($newDir.'/thumb');
mkdir($newDir.'/web');
}
}
$mkfolder .= DIRECTORY_SEPARATOR;
}

if ($this->dirExists($newDir))
{
return true;
} else {
return false;
}
}
}
?>

***** photo.php *****


<?php
class Photo
{
public $photo;
public $aspect;
public $photoOrigWidth = 0;
public $photoOrigHeight = 0;
public $origFileSize = 0;
public $photoType;
public $photoBaseName;

function __construct($photofile)
{
$this->photo = $photofile;
$this->dimensions();
}
Page 119 of 126

private function dimensions()


{
$dims = getimagesize($this->photo);
$this->origFileSize = filesize($this->photo);
$this->photoOrig['w'] = $dims[0];
$this->photoOrig['h']= $dims[1];
$this->mimeToWord($dims[mime]);
$this->setAspect($dims[0],$dims[1]);
}

private function mimeToWord($fred)


{
switch ($fred){
case "image/jpeg":
$tested = "JPEG"; break;
default:
$tested = "Unknown";
}
$this->photoType = $tested;
}

public function setAspect($wide,$high) {


if ($wide/$high >= 1) {
$this->aspect = "landscape";
} elseif ($wide/$high == 0) {
$this->aspect = "square";
} else {
$this->aspect = "portrait";
}
}

public function dispFileSize($filebytes)


{
$filekb = $filebytes/1024;
$filemb = $filekb/1024;
if ($filemb >= 1 ) {
$filenice = round($filemb, 2). "MB";
} else {
$filenice = round($filekb, 2). "KB";
}
return $filenice;
}

public function createWebPics() {


if ($this->photoType != "Unknown") {
$this->convertPhotos('tiny',60,60);
$this->convertPhotos('thumb',100,100);
$this->convertPhotos('web',500,500);
}

function convertPhotos($type,$wide,$high) {
$size = $this->calculateDimensions($this->photoOrig['w'],$this-
>photoOrig['h'],$wide,$high);
Page 120 of 126

$load = 'ImageCreateFromJpeg';
$save = 'ImageJpeg';
$newFile = $this->newFileName($type);
$srcPhoto = $load($this->photo);
$destPhoto = imageCreateTrueColor($size['w'],$size['h']);
imagecopyresampled($destPhoto,$srcPhoto,0,0,0,0,
$size['w'],$size['h'],$this->photoOrig['w'],$this->photoOrig['h']);
$save($destPhoto,$newFile);
}

function newFileName($type) {
if ($type == 'orig') {
$newFile = $this->photo;
} else {
$pathInfo = pathinfo($this->photo);
$newFile = $pathInfo['dirname'].'/'.$type.'/'.$pathInfo['filename'].'-
'.$type.'.'.$pathInfo['extension'];
}
return $newFile;
}

function calculateDimensions($width,$height,$maxWidth,$maxHeight) {
$ret = array('w' => $width, 'h' => $height);
$ratio = $width/$height;
if ( $width > $maxWidth || $height > $maxHeight) {
$ret['w'] = $maxWidth;
$ret['h'] = $ret['w']/$ratio;

if ($ret['h'] > $maxHeight) {


$ret['h'] = $maxHeight;
$ret['w'] = $ret['h'] * $ratio;
}
}
return $ret;
}

function show()
{
$fs = $this->dispFileSize($this->origFileSize);
echo "This is the file [$this->photo] <br /> The photo is [$this->aspect] <br />
This is the original Width [".$this->photoOrig['w']."]<br />
This is the original Height [".$this->photoOrig['h']."]<br />
The photo type is [$this->photoType]<br />
The original file size is [$fs]<br />";
}

function __toString()
{
return var_export($this, true);
}
}
?>

***** mapit.php *****


Page 121 of 126

<?php
if (isset($_GET['message'])) {
$message = trim(strip_tags(stripslashes($_GET['message'])));
} else {
$message = '';
}
?>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"


"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">


<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>

<title>Google map stuff</title>


<script src="http://maps.google.com/maps?file=api&amp;v=2&amp;key=ABQIAAAAWBs-
8BZ4TjrRayH7tC7eahSQyRsya9AfG3FfrJQ0LsXZ4YxFeBRkUOIFOoHML_01ujzsfpBPoMadXA"
type="text/javascript"></script>
<script type="text/javascript" src="functions.js"></script>
<link rel="stylesheet" type="text/css" href="style.css" />

</head>

<body onload="init('map','message')">
<div id="main">
<div id="map"></div>
<div id="formwrapper">
<?php if (strlen($message) > 0) { ?>
<div id="messages">
<?php echo htmlentities($message); ?>
</div>
<?php } else { ?>
<div id="messages" style="display: none"></div>
<?php } ?>

<h3>Add a new location</h3>


<form method="post" action="process_form.php" onsubmit="submitForm(this);
return false;">
<table>
<tr>
<td>Name:</td>
<td><input type="text" name="locname"
maxlength="150" /></td>
</tr>
<tr>
<td>Address:</td>
<td><input type="text" name="address"
maxlength="150" /></td>
</tr>
<tr>
<td>City:</td>
<td><input type="text" name="city"
maxlength="150" /></td>
Page 122 of 126

</tr>
<tr>
<td>County:</td>
<td><input type="text" name="county"
maxlength="150" /></td>
</tr>
<tr>
<td>Postcode:</td>
<td><input type="text" name="postcode"
maxlength="150" /></td>
</tr>
<tr><td>
&nbsp;
</td></tr>
<tr><td>
<input type="submit" value="Add Location" />
</td></tr>
<tr><td>
&nbsp;
</td></tr>
<tr><td colspan="2">
<A HREF="http://nearby.org.uk"
TARGET="_blank">PostCode to co-ordinates at nearby.org.uk</A>
</td></tr>

</table>
</form>
</div>
</div>
</body>
</html>

***** process_form.php *****


<?php
//process_form.php

require_once('dbconnector.php');
require_once('locations.php');
opendatabase();

$ajax = (bool) $_POST['ajax'];

$values = array('locname' => '',


'address' => '',
'city' => '',
'county' => '',
'postcode' => '');

$error = false;

foreach ($values as $field => $value) {


$val = trim(strip_tags(stripslashes($_POST[$field])));
$values[$field] = mysql_real_escape_string($val);
if (strlen($values[$field]) == 0) {
Page 123 of 126

$error = true;
$fred = $values[$field];
}
}

//Get the longitude and latitude


$location = new Locations($values['postcode']);
$values['latitude'] = $location->lat;
$values['longitude'] = $location->long;

if ($error) {
$message = 'Error adding Location'.$fred;
} else {
$query = sprintf("insert into store (%s) values ('%s')", join(', ', array_keys($values)),join("',
'",$values));
mysql_query($query);
$message = 'Location has been added: '.$values['locname'];
}
if ($ajax) {
echo $message;
} else {
header('Location: mapit.php?message=' . urlencode($message));
exit;
}
?>

***** dbconnector.php *****


<?php
//dbconnector.php
//define mysql connect variables
define ("MYSQLHOST","localhost");
define ("MYSQLUSER","apressauth");
define ("MYSQLPASS","blahblah");
define ("MYSQLDB","taskdb");

$GLOBALS['host'] = 'localhost';
$GLOBALS['user'] = 'apressauth';
$GLOBALS['pass'] = 'blahblah';
$GLOBALS['db'] = 'taskdb';

function opendatabase(){
$db = mysql_connect($GLOBALS['host'],$GLOBALS['user'],$GLOBALS['pass']);
try {
if (!$db){
$exceptionstring = "<em>Error connecting to database:</em> <br />";
$exceptionstring .= mysql_errno() . ": " . mysql_error();
throw new exception ($exceptionstring);
} else {
mysql_select_db ($GLOBALS['db'],$db);
}
return $db;
} catch (exception $e) {
echo $e->getmessage();
Page 124 of 126

die();
}
}

?>

***** locations.php *****


<?php
class Locations {

public $locCode;
private $nearbyKey = '90b6be422e81fc';
private $uri = 'http://www.nearby.org.uk/api/convert.php';
public $lat;
public $long;

function __construct($postCode) {
$this->locCode = preg_replace('/\s+/','',$postCode);
$this->getLatLong();
}

function getLatLong() {
$URL = $this->createURL();
include("$URL$this->locCode");
$this->lat = $convert_output['ll-wgs84']['lat'];
$this->long = $convert_output['ll-wgs84']['long'];
}

function createURL() {
$URL = $this->uri.'?key='.$this->nearbyKey.'&output=php&p=';
return $URL;
}
}
?>

***** function.js *****


//functions.js
//div to hold the map
var mapContainer = null;

//co ords for SW4 0LE


var mapLng = -0.143320;
var mapLat = 51.465471;
var mapZoom = 2;
var locationsXml = 'locations.php';

function trim(str) {
return str.replace(/^(\s+)?(\S*)(\s+)?$/, '$2');
}

function showMessage(msg) {
msgContainer = document.getElementById("messages");
Page 125 of 126

if (msg.length == 0)
msgContainer.style.display = 'none';
else {
msgContainer.innerHTML = msg;
msgContainer.style.display = 'block';
}
}

function init(mapId, msgId) {


// showMessage('');
mapContainer = document.getElementById(mapId);
msgContainer = document.getElementById(msgId);
loadmap();
}

function createInfoMarker(point, theaddy) {


var marker = new GMarker(point);
GEvent.addListener(marker, "click",
function () {
marker.openInfoWindowHtml(theaddy);
}
);
return marker;
}

function loadmap() {
var map = new GMap(mapContainer);
map.addControl(new GMapTypeControl());
map.addControl(new GSmallMapControl());
map.centerAndZoom(new GPoint(mapLng, mapLat), mapZoom);
var request = GXmlHttp.create();
request.open('POST','location.php',true);
request.onreadystatechange = function() {
if (request.readyState == 4) {
var xmlDoc = request.responseXML;
var markers = xmlDoc.documentElement.getElementsByTagName("marker");
for (var i = 0; i < markers.length; i++ ) {
var point = new
GPoint(parseFloat(markers[i].getAttribute("longitude")),

parseFloat(markers[i].getAttribute("latitude")));
var theaddy = '<div class="location"><strong>'
+ markers[i].getAttribute('locname') +
'</strong><br />'
+ markers[i].getAttribute('address') + '<br
/>'
+ markers[i].getAttribute('city') + ','
+ markers[i].getAttribute('county') + '<br
/>'
+ markers[i].getAttribute('postcode')
+ '</div>';
var marker = createInfoMarker(point, theaddy);
map.addOverlay(marker);
}
Page 126 of 126

// var polyline = new GPolyline([new GPoint(-


0.137574481662604,51.4616028588849),new GPoint(-
0.143319752644715,51.4654708660116)],"#ff0000",10);
// map.addOverlay(polyline);
}
}
request.send('a');
}

function submitForm(frm) {
// alert("This is frm["+frm+"]");
showMessage('');
var fields = {
locname : 'You must enter the location name',
address : 'You must enter a address',
city : 'You must enter the city',
county : 'You must enter a county',
postcode : 'You must enter a postcode'
};
var errors = [];
var values = 'ajax=1';
for (field in fields) {
val = frm[field].value;
if (trim(val).length == 0) {
errors[errors.length] = fields[field];
}
values += '&' + field + '=' + escape(val);
}
if (errors.length > 0 ) {
var errMsg = '<strong>The following errors have occured: </strong><br /><ul>\n';
for (var i = 0; i < errors.length; i++) {
errMsg += '<li>' + errors[i] + '</li>\n';
}
errMsg += '</ul>\n';
showMessage(errMsg);
return false;
}
mapContainer = document.getElementById("map");
mapContainer.innerHTML = "<b>Loading map....</b>";
// alert("This is values["+values+"]");
var xmlhttp = GXmlHttp.create();
xmlhttp.open("POST",frm.action, true);
xmlhttp.setRequestHeader("Content-Type","application/x-www-form-urlencoded;
charset=UTF-8");
xmlhttp.onreadystatechange = function() {
if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
showMessage(xmlhttp.responseText);
}
}
xmlhttp.send(values);
setTimeout("loadmap()",1000);
}

You might also like