Looking for New Clients

My consulting company, Seabourne Consulting, is growing and we’re looking for new clients.  We specialize in web development, information strategic planning and project management.  We do a lot of work building websites that bring people together using new and interesting technologies.  We can work with any budget on any project — we do complete websites, from design to implementation, or we can also help on an existing project, or provide strategic advice.

If you are interested in learning more, we’ve put together a two page overview of Seabourne and a sample of some of our work.  If you are interested in learning more or talking about a project, you can contact me at 425-296-2440, or via email here.  Also, checkout our complete portfolio at http://seabourneconsulting.com.

  • Share/Bookmark

GCal4Ruby Now on Git

Thanks to everyone who has supported and contributed to GCal4Ruby over the last year.  To make it easier for everyone to continue to use and improve the library, I’ve moved the code over to Git from the old SVN repository.  You can now find the latest code at github.com/mjreich/GCal4Ruby.git.  I’ve also moved the associated GData4Ruby and GDocs4Ruby libraries over as well.  You can find them at http://github.com/mjreich.

PS – I’m working on getting a new version (0.5.5) done and posted, hopefully in the next couple of weeks.

  • Share/Bookmark

New Seabourne Consulting Site and Logo

Seabourne Logo

I’m proud to announce that the Seabourne Consulting website has a new logo and design. I’ve spent a couple of months refining and getting it implemented, but I’m really happy with the results.

Seabourne Consulting is my company through which I provide consulting services for clients.  We focus on providing web development and technical consulting for not-for-profit organizations.  I started Seabourne in 2009 and have had a great year so far.  Clients have included the Building Materials Reuse Association, the Association of Metropolitan Water Agencies and the US Environmental Protection Agency.

Seabourne provides a range of services to clients, including:

  • Website development using content management systems.
  • Developing online payment solutions for accepting credit card payments online.
  • Information and technology strategy consulting.
  • Graphic design for print and the web.
  • Internet application development.
  • Long-term support and assistance.

As a company, the goal is to:

  • Provide affordable technical services with exemplary project management, communication and customer focus. I have created a unique project management system that I use with all my clients that gives access to all information about your project, including what I am working on, tasks, project calendar, and project management information (including hours spent, invoices and the contract).  This information is readily available to my clients via my company website, www.seabourneconsulting.com.
  • Contribute to open-source initiatives and software. Where possible, I use and contribute to open source projects.  I’m the creator and maintainer of three open source libraries, and am always looking to contribute to more.

If you are interested in the services we offer or in talking about a potential project, head on over to http://seabourneconsulting.com and take a look at the projects I’ve done, or contact me directly.

  • Share/Bookmark

Tuning Apache Instances

I’ve been running a new VPS instance for the last few months and started getting some weird Passenger errors. On further research, it looked like passenger was hitting out of memory conditions and unable to spawn a new worker. After looking at the running process, I saw that apache was spawning more than 10 processes over time, and they were eating up all the memory for my vps.

So an easy solution was to reduce the number of child process servers Apache can spawn. In your httpd.conf file, simply edit down the number of MinSpareServers and StartServers to something your system can handle. In my case, the relevant lines look like this:

<IfModule prefork.c>
StartServers       1
MinSpareServers    1
MaxSpareServers    5
MaxClients         256
MaxRequestsPerChild  4000
</IfModule>
  • Share/Bookmark

WordPress vs Joomla!

Most web sites these days use a Content Management System (CMS) to manage and display content.  At its most basic, a content management system provides an interface for creating and structuring content that is displayed on a website.  A good CMS can make managing a website easy for the non-technical user, and through blogging software (a simple kind of CMS), CMS powered sites have opened up web publishing to the masses.  Currently, there are as many CMS solutions as there are industrious programmers, which is to say, quite a few.  The most popular tend to fall into two main categories:

  • Site-in-a-box solutions: these were developed to make creating and managing a website as simple as installing an application.  However, they can be used to create sites that are quite complicated, and reflect this complexity in their management interfaces (look no further than Drupal’s really unintuitive system of nodes and node/content types).  Rich APIs and plugin architectures are a hallmark, allowing for third party extensions that expand on the core functionality.  Examples include Joomla, Sharepoint and Drupal.
  • Began-life-as-a-blog solutions: these system go for simplicity over complexity, and often place an emphasis on design and presentation rather than structures and data accessibility.  However simple they may have started, most are now full fledged CMS solutions providing theming, plugins and all the basics that a developer needs to create a full-featured website.  Examples are WordPress and Movable Type.

So, as a site developer, a CMS is great choice from which to build a website for a client.  It has the obvious selling point of being user friendly and easily manageable for clients to continue updating once the site is delivered.  However, you now have to choose between the many great solutions out there. This article will highlight the advantages and disadvantages of two major free CMS solutions, Joomla and WordPress, each one from a different category listed above.  This is not a technical comparison (i.e. which is faster, better developed, etc), but rather a look at the design, features and overall usefulness of each.  I’ve developed sites using both solutions, and have received a good amount of feedback from clients that I’ll integrate into my analysis of both.

Joomla!

Joomla! is a great full featured CMS.  Built in PHP and using MySQL as the backend database, Joomla! provides a rich theming architecture and a solid API for building plug-ins and extensions.  One of the main attractions of Joomla! is its perceived ease of use – it has a very straightforward content model – and its large number of freely available plug-ins.  Need a discussion forum for your site?  Joomla! probably has a free plug-in for you (try Fireboard).

Common criticisms of Joomla! include its speed.  Each page load usually requires over 20 separate SQL queries, and this can be noticeable on a high-traffic site.  Also, as a developer, I’ve often been frustrated with the poor quality and integration of third party extensions.  That discussion forum above?  It may be a breeze to find an install, but once you get it plugged into your site, its going to take hours of CSS and PHP tweaking to keep it from sticking out like a sore thumb in your site design.

Setting up a Site in Joomla!
Getting Joomla! up and running is pretty simple.  It has an easy plug-n-play installer that gets you all set up and generates the necessary configuration files automatically.  It also includes a few generic templates.  However, creating your initial site structure and content can be fairly complicated and not always intuitive.  One of my particular gripes about Joomla! is that if an article is not explicitly referenced as a page in a menu hierarchy, you can’t link to it directly.  This means that you have to go through the time consuming task of creating your menus in Joomla!, correctly referencing all your content pages, then integrate your menu into a custom template.  This is great if you are changing your content and structure frequently the site menus updated automatically.  But for most websites this is a huge pain in the ass.

Theming
Joomla! uses a less-is-more approach to theming.  Your site theme is specified in one file (an index.php file in a theme folder), and includes all display code that is used on any page, even if you want different layouts, etc.  You make lots of use of if defined statements to catch when a sidebar is displayed, for example. Determining what is actually displayed on each page is done entirely within the administration interface, by specifying display areas that you’ve defined in your template file (sidebar1, sidebar2, etc).

In theory, this is great, but I find that developing themes can be really complicated and time consuming because you are going between the code and administration interface much too often.  If you want to create a new location for a widget, you have to create the <div> in the template file, then navigate through about 5 different menus in the admin interface to assign the widget to the new location.

Everyday Use
I had high hopes for Joomla!.  I originally used it because I thought that I could implement it, write an administration manual for the client and then send them off on their way.  Unfortunately (or fortunately if you look at from a business perspective) that was not the case.  Fundamentally, Joomla! suffers from three very big shortcomings:

  1. It is just too complicated for tech un-savvy people to use.  The administration interface is big, complicated and often unclear as to what you need to do to accomplish even a basic task.  Editing an article takes a lot of effort, and I found that my clients just found it too intimidating.
  2. The Joomla! user interface is awful.  Related to the point above, this stems from the fact that Joomla! is very clearly an open source project that is designed by committee.  Stupid interface decisions make it a total pain in the ass to work with.  One example: for some unfathomable reason, Joomla! decides to aggressively lock any resource you are editing, and while locked, you can’t use any of the navigation menus until you explicitly click the ‘Cancel’ or ‘Save’ button.  All you have to indicate that you are stuck is a very light gray shading over the menu items, which is indiscernible on most computers.
  3. Security is non existent.  I’ve had all of my Joomla! based sites fall victim to successful hacking attempts.  Luckily most have ended with simple spam injections, but I’ve been amazed with the ease hackers seem to gain access to a Joomla! system.  The community seems aware of this, but security patches are few and far between.

Bottom line: Joomla! is powerful, complicated and feature rich.  However, poor interface design decisions and its susceptibility to hacking make it a painful experience for day-to-day use.

WordPress

WordPress started as a basic blogging software but has expanded into a pretty full featured CMS.  Also built with PHP and using MySQL as the backend database, WordPress provides a superb user interface, rich theming and extension ecosystem, and pretty good reliability.  I originally started using WordPress as the software for this blog, but have used it for a few websites now and am really impressed with it as a full features CMS.

Setting up a Site in WordPress
Two words: dead simple.  Getting a site up and running is a painless install process of filling out five fields with DB info.  After that, it all done through the admin interface.  WordPress includes a great selection of themes, all of which are available for searching and installation directly through the administration interface.  This is awesome.  You can have a site set up with a brand new, customized template in 30 minutes and all without touching any code.

Installing plug-ins and widgets is also a breeze.  Again, the plug-in directory is searchable through the admin interface directly, saving you a lot of time.  There is a growing library of plug-ins available for WordPress now, and many provide functionality you would only expect to find in a more complicated CMS like Joomla! or Drupal.

Theming
Ironically, theming in WordPress feels more technical than in Joomla!.  This is interesting for a system that is so much more user friendly in every other aspect.  WordPress themes require a minimum of five different php pages if you are creating a complete theme.  The index.php theme file is used as a fallback if a specific layout can’t be found, say page.php for a regular page.  Additionally, you can create arbitrary php functions which are included in the functions.php file.

I’m really torn about the WordPress theming system.  Part of me really likes having complete control over the code that you get with the granular files and functions of a WordPress theme.  But I also wish there was a more elegant solution that matched the rest of the great conceptual design of the WordPress system.  However, once you get a theme setup and installed, it is a breeze to administer and alter without ever really having to touch the code again.  WordPress includes a great drag-and-drop interface for applying widgets to your theme, and you can install new widgets through the same interface without having to edit your theme file.

Everyday Use
WordPress is a joy to use.  Every element of the software is beautiful, functional, and clearly a lot of effort went into the design.  One thing that keeps making my day is the little ways that the administration interface makes your job easier.  From being able to edit every setting within 1 click of the main menu, to automatically alerting you of new plug-ins and core versions, WordPress makes administration simple and easy.

Clients love it too.  Adding a new page, or post is dead simple and doesn’t require any technical expertise at all.  I’ve found that my clients really love the integrated stats system (actually a plugin, but designed by Automattic) as well as the simple WYSIWYG article editor.

So, which one should I use?

Good question.  All things being equal, my time and effort is going into WordPress powered sites.  Reasons include:

  • Ease of deployment. Great installer and support make it fantastic to set up.
  • Ease of development. Themes are a little complicated, but with the complexity comes great control over your sites appearance.
  • Ease of use.  WordPress is just nice to use.  The administration interface is the best out there.  I have many moments where I think ‘Man, that’s a cool/handy/well designed feature.”  Clients also find it easy to use and it allows them to get their jobs done without having to come to me for support.
  • Share/Bookmark

Great Resource for Color Palettes

Found a great resource for color palettes, designs and all around great inspiration: COLOURlovers.  They have a great selection of color schemes that are user submitted, in addition to background patterns and other resources for web and print.  Great resource to get you started on your next project.

  • Share/Bookmark

GCal4Ruby Example Application

After many requests, I’ve put together a sample Ruby on Rails application that implements most of the functionality of the GCal4Ruby gem.  You can find the source here: http://examples.cookingandcoding.com/gcal4ruby/gcal4ruby_example.zip.

Functionality that’s implemented in the sample includes:

  • Creating new events
  • Creating new calendars
  • Updating, deleting events/calendars
  • Searching for Events and Calendars
  • Displaying a list of events for a calendar
  • Adding recurrence to an event
  • Adding/Removing event reminders
  • Adding/Removing event attendees
  • Share/Bookmark

GCal4Ruby version 0.5.0 Released

I’ve just pushed a new version of the GCal4Ruby gem out tonight.  Thanks to everyone who has contributed feedback and bug reports over the last year.

Version 0.5.0 is a pretty major rewrite of the code, and there are correspondingly large changes to the interfaces.  Below is a brief summary of the major differences.  Be sure to check out the documentation and new project page at http://cookingandcoding.com/gcal4ruby/ for more information.

  1. Complete rewrite of the backend to use GData4Ruby. As most of you probably have noticed, the base.rb and service.rb classes were in dire need of refactoring and improvement.  To facilitate that, and also to make the base Google Data API classes reusable, I’ve pulled the new code into a new gem, GData4Ruby, and used the new GData4Ruby classes as the base for the GCal4Ruby objects.  This has removed all of the low level code, and allowed for more robust handling and performance.
  2. Updated Calendar Class. The calendar class has seen improvements, mostly in how the #find and #to_iframe methods work.  You’ll notice that both these methods have new parameters, aimed at simplifying and future-proofing the code.  Now you can pass name/value pairs through the args hash and they’ll be appended to the query string directly.  This also opens up support for any parameters that weren’t supported in previous versions.  Check out the documentation for more info.
  3. Updated Event Class.  The event class has seen the biggest improvements.  First off, the event class is service centric, rather than calendar centric.  That means that you’ll need to instantiate an event with a service rather than a calendar.  The calendar is set using the ‘calendar’ attribute.  Second, there are now a whole lot more attributes available as the Event class is a subclass of the GDataObject class and gets all its goodness.  For example, full event statuses are now supported.  I’ve also made corresponding changes to the #find method which mirror those made for the Calendar class.  This should simplify and future proof the code for new versions of the API.

In addition to the above three major areas of changes, there are improvements sprinkled throughout that make the gem more consistent and reliable.  Give it a go and let me know what you think, and as always, post any bugs you find on the mailing list.

  • Share/Bookmark

Accessing Google Documents with Ruby on Rails

I’m building a basic project management system, and one of the requirements is that I want all my project’s documents to be stored and available online, exportable into multiple formats, shared among multiple users who have the ability to work collaboratively on the same document if need be.  Normally, this would take a lot of work to implement – its a hefty list of requirements.  But, this is exactly the functionality that Google Documents offers…wouldn’t it be great to be able to tap into the work done by Google and leverage it for my own site?

The answer is an obvious yes, and now its possible and quite easy thanks to the Google Documents API.  I’ve created a full featured Ruby Gem that implements all of the features available through the Documents API: GDocs4Ruby.

This tutorial will walk through the basic steps of creating a document management system that uses the Google Documents system as the data storage back-end.  I’ll be coding the front-end in Ruby on Rails.

The source for all the examples discussed below is available here.

Basic Requirements

Here’s a list of the basic requirements for the application:

  1. Users can view a list of documents and folders stored in the system.
  2. Each project has a documents folder where all related documents are stored.
  3. Users can upload documents from their computer.
  4. Users can create new documents.
  5. Users can delete documents.
  6. Users can edit the content of the document.
  7. Multiple users can collaboratively edit documents.

It seems like a big list, but luckily this will all be pretty easy to do.

Application Setup

First step is to create a new rails structure, I’ll be calling it ‘GDocs4Ruby_Example’.

> rails gdocs4ruby_example

Next, we’ll create the controller for the application, called Documents.

> script/generate controller Documents

So, if we were creating a normal application, we would create our models at this point. But in this case, because we’ll be storing everything on the Google servers, we’ll use the classes included in the GDocs4Ruby library as our models. They contain similar methods to ActiveRecord classes, so they integrate nicely with Ruby on Rails code.

Setting up the Session
The first thing we need to do is setup the GDocs4Ruby#Session class. This will handle all of the communication with the Google servers, and provide the authentication interface for our user.

To enable everyone to see the same list of files, we’ll want to have a base user that is used to access the Google Documents API.  We could put a local user authentication system on top of this to ensure that only our project staff have access to the system, but the backend interactions will all happen under one Google account.

What we will do is create a ApplicationController method that sets up the Service object every time a request is made, called @account in the examples. Then we’ll add a before_filter to the Documents controller that ensures this setup method is called when we need to work with the Google servers.

require 'GDocs4Ruby'
class ApplicationController < ActionController::Base
  include GDocs4Ruby

  helper :all # include all helpers, all the time
  protect_from_forgery # See ActionController::RequestForgeryProtection for details

  # Scrub sensitive parameters from your log
  # filter_parameter_logging :password

  def setup
    @account = Service.new()
    @account.debug = true
    @account.authenticate(USERNAME, PASSWORD)
  end
end

USERNAME and PASSWORD should be constants containing the credentials for the shared account.

Here’s the code for the Documents controller.

class DocumentsController < ApplicationController
  before_filter :setup
end

Now we have an @account object available whenever we need to interact with the Google servers.

Getting a List of Folders and Documents
The first page we want our users to see is a list of folders and documents, so they can select where to go next.  I’ll use a basic 2 column layout that is familiar to everyone, with the folders on the left and the documents on the right.

First, we’ll get a list of the documents and folders in the controller, then pass that onto the view to display. I’ll also want to be able to reuse this same code for viewing the contents of folders, so I’ll add an if statement that checks to see if a folder_id parameter has been passed, and load the variables accordingly. In the Documents controller, add:

  def index
    if not params[:folder_id]
      #Display all files and root folders
      @documents = @account.files
      @folders = @account.folders.select{|f| !f.parent } #display only root folders
    else
      #Display only files and folders contained by folder_id
      @folder = Folder.find(@account, {:id => params[:folder_id]})
      @documents = @folder.files
      @folders = @folder.folders
    end
  end

If no folder is specified we grab the root files and folders from the @account Service object. Otherwise, we find the specified folder and load the child folders and files.

In the corresponding .erb file:

<% @title = 'Documents' %>
<table>
	<tr>
		<td style='width: 40%; vertical-align: top;'>
			<strong>Folders</strong>
			<ul>
			<% @folders.each do |f| %>
				<li>
					<%= link_to f.title, :action => :index, :folder_id => f.id %> (<%= link_to 'X', {:action => :delete, :folder_id => f.id}, :confirm => 'Are you sure you want to delete this Folder?' %>)
					<%= render :partial => 'folders', :locals => {:folder => f} %>
				</li>
			<% end %>
			</ul>
		</td>
		<td style='vertical-align: top;'>
			<strong>All Documents</strong>
			<%= render :partial => 'file_list' %>
		</td>
	</tr>
</table>

Following DRY practices, I’ve seperated the common display code for folders and files into two partials: ‘folders’ and ‘file_list’.

Here’s the ‘folders’ partial:

<ul>
	<% folder.sub_folders.each do |f| %>
	<li>
		<%= link_to f.title, :action => :index, :folder_id => f.id %> (<%= link_to 'X', {:action => :delete, :folder_id => f.id}, :confirm => 'Are you sure you want to delete this Folder?' %>)
		<%= render :partial => 'folders', :locals => {:folder => f} %>
	</li>
	<% end %>
</ul>

and the ‘file_list’ partial:

<ul>
	<% @documents.each do |doc| %>
	<li>
		<% if not doc.viewed %>
			<strong>
		<% end %>
		<%= link_to doc.title, :action => :view, :doc_id => doc.id %> <em><%= doc.type.capitalize %></em>(<%= link_to 'X', {:action => :delete, :doc_id => doc.id}, :confirm => 'Are you sure you want to delete this file?' %>)
		<% if not doc.viewed %>
			</strong>
		<% end %>
	</li>
	<% end %>
</ul>

Basically, these display the folders and files within <ul> tags, and add links to drill down into folders and view the details for files.
Now, if you run the code and view ‘/documents/’ in your browser, you should see a list of files and folders.

Viewing File Details
The next step is to implement a basic file view. When a user clicks on a file, we want them to be able to view the metadata for the file (i.e. title and author and updated date), download a copy of the file in different formats, and manage the file’s folders and user permissions. This will all be done through the ‘view’ page.
First, add a new method to the Documents controller:

  def view
    @document = BaseObject.find(@account, {:id => params[:doc_id]})
  end

This takes the passed parameter ‘doc_id’ and loads the Document. Because we don’t know which type of file this is necessarily (i.e. Document or Spreadsheet) we’ll use the BaseObject#find method.
For the view.html.erb file, add:

<% @title = 'View Document Info' %>
<p><%= button_to_function 'Edit', "window.location = '#{url_for(:action => :edit, :doc_id => @document.id)}'" %> <%= button_to_function 'Edit in iFrame', "window.location = '#{url_for(:action => :edit_iframe, :doc_id => @document.id)}'" %>
<p><strong>Title:</strong> <%= @document.title %></p>
<p><strong>Author:</strong> <%= @document.author_name %> (<%= @document.author_email %>)</p>
<p><strong>Type:</strong> <%= @document.type %></p>
<p><strong>Published: </strong><%= Time.parse(@document.published).strftime("%m/%d/%Y") %></p>
<p><strong>Updated: </strong><%= Time.parse(@document.updated).strftime("%m/%d/%Y") %></p>

<h3>Folders</h3>
<% form_for '', :url => {:action => :update_doc_folder, :doc_id => @document.id, :do => 'add'} do %>
<p><%= select_tag :folder, options_for_select(@account.folders.select{|f| !@document.folders.include?(f.title) }.collect{|f| [f.title, f.id]}) %> <%= submit_tag 'Add to Folder' %></p>
<% end %>
<ul>
	<% @document.folders.each do |f| %>
		<li><%= f %> (<%= link_to 'X', {:action => :update_doc_folder, :doc_id => @document.id, :do => 'delete', :folder => f} %>)</li>
	<% end %>
</ul>
<h3>Download As:</h3>
<%= render :partial => @document.type+"_download" %>

<h3>Permissions</h3>
<strong>Add User</strong>
<% form_for '', :url => {:action => :add_user, :doc_id => @document.id} do %>
<p>Email: <%= text_field_tag :user %> Role: <%= select_tag :role, options_for_select(['writer', 'reader']) %> <%= submit_tag 'Save' %>
<% end %>
<ul>
<% @document.access_rules.each do |a| %>
	<li><%= a.user %> - <%= a.role %> <% if a.role != 'owner' %>(<%= link_to 'X', {:action => :remove_user, :user => a.user, :doc_id => @document.id}, :confirm => 'Are you sure you want to remove this user?' %>)<% end %></li>
<% end %>
</ul>

There are four main sections of the view: file metadata, download links, folders and user permissions.

For the file metadata, we display the title, type, author and published/updated dates. This is all loaded automatically by the Document object.

For the download links, we need to provide different download types depending on the document type. In other words, Google provides different types of file exports for each Document, Presentation or Spreadsheet. So a document can be exported as RTF, while a spreadsheet can be exported as XLS. So we’ll use a partial to display the allowed download types for each file, for example ‘document_download’. Here’s the example of the document partial:

<%= link_to 'Doc', :action => :download, :doc_id => @document.id, :type => 'doc' %>
<%= link_to 'HTML', :action => :download, :doc_id => @document.id, :type => 'html' %>
<%= link_to 'ODT', :action => :download, :doc_id => @document.id, :type => 'odt' %>
<%= link_to 'PDF', :action => :download, :doc_id => @document.id, :type => 'pdf' %>
<%= link_to 'PNG', :action => :download, :doc_id => @document.id, :type => 'png' %>
<%= link_to 'RTF', :action => :download, :doc_id => @document.id, :type => 'rtf' %>
<%= link_to 'TXT', :action => :download, :doc_id => @document.id, :type => 'txt' %>
<%= link_to 'ZIP', :action => :download, :doc_id => @document.id, :type => 'zip' %>

and the download method in the controller looks like this:

  def download
    @document = BaseObject.find(@account, {:id => CGI::unescape(params[:doc_id])})
    send_data @document.get_content(params[:type]), :disposition => 'inline', :filename => "#{@document.title}.#{params[:type]}"
  end

To display the folders the document is contained in, we simply access the folders attribute of the document object and iterate through the returned array.

Its a similar procedure to display the users who have access to the document; we iterate through the array returned by the access_rules method. Each object in the array is a GData4Ruby#AccessRule that contains a username and role. The roles for Google Documents may either be ‘writer’ or ‘reader’ depending on whether user can edit the file.

Editing a File
There are two ways you can edit a Google Documents file:

  1. Download the raw HTML and use a WISYWIG text editor, like TinyMCE, to edit the HTML, then save the edited HTML back to Google. This is a great solution for documents, however it won’t work for presentations or Google Spreadsheets.
  2. For spreadsheets and presentations, display the file as an embedded iframe, allowing you to use Google’s UI for editing the file. The upside of this approach is that you get all the great benefits of using the Google interface, and are able to edit collaboratively with other users. The downside is that all users (at least with version 2.0 of the API) have to be specifically added as ‘writers’ to the file.

There are two links at the top of the view page that provide access to these two editing methods. There is a great helper method for generating the iframe, BaseObject#to_iframe, that you can use to create the editing window.  Here is the view code for grabbing the Document content to edit (as HTML):

<% form_for '', :url => {:action => :save_content, :doc_id => @document.id} do %>
<p><%= text_area_tag 'content', @document.get_content('html'), :size => '50x10' %></p>
<%= submit_tag 'Save Content' %>
<% end %>

and for saving the metadata and content back to Google:

  def save
    @document = BaseObject.find(@account, {:id => CGI::unescape(params[:doc_id])})
    @document.title = params[:title]
    @document.save
    redirect_to :action => :view, :doc_id => @document.id
  end

  def save_content
    @document = BaseObject.find(@account, {:id => CGI::unescape(params[:doc_id])})
    @document.put_content(params[:content])
    redirect_to :action => :view, :doc_id => @document.id
  end

Uploading New Content
Now that the file interface is done, we want to be able to existing content to our Google account, i.e. upload a file. There are two cases where this can be used:

  1. Uploading a new file
  2. Uploading new content to an existing file

Since these are essentially the same functions, we’ll combine it into a single method in the Document controller.

  def send_upload
    if params[:doc_id]
      doc = BaseObject.find(@account, {:id => params[:doc_id]})
      doc.content = params[:upload_file].read
      doc.content_type = File.extname(params[:upload_file].original_filename).gsub(".", "")
      if doc.save
        flash[:notice] = 'File successfully uploaded'
      else
        flash[:warning] = 'Could not upload file!'
      end
      redirect_to :action => :view, :doc_id => doc.id and return
    else
      file = BaseObject.new(@account)
      file.title = params[:upload_file].original_filename.gsub(/\.\w.*/, "")
      file.content = params[:upload_file].read
      file.content_type = File.extname(params[:upload_file].original_filename).gsub(".", "")
      if file.save
        flash[:notice] = 'File successfully uploaded'
      else
        flash[:warning] = 'Could not upload file!'
      end
      redirect_to :action => :index and return
    end
  end

First, we want to check to see if a document ID is passed, in which case we’ll know we want to pass the new content to an existing file. We do this by setting the ‘content’ and ‘content_type’ attributes for the Document with the passed params.
Otherwise, we create a new Document and set the ‘content’ and ‘content_type’ attributes, and save the file.

Deleting a File or Folder
Deleting a file is simple and should be familiar now:

  def delete
    obj = nil
    if params[:doc_id]
      obj = BaseObject.find(@account, {:id => params[:doc_id]})
    elsif params[:folder_id]
      obj = Folder.find(@account, {:id => params[:folder_id]})
    end
    if obj and obj.delete
      flash[:notice] = 'Successfully deleted!'
    else
      flash[:notice] = "Error deleting!"
    end
    redirect_to request.referer
  end

Here, we check for either a file or folder id, grab the referenced file/folder and delete it if it exists.

Access Rules
To enable a different user to access a document, we need to add an Access Rule. This requires a user id, usually an email address, and a role, either ‘writer’ or ‘viewer’. We’ll need three methods to handle adding, updating and removing access rules:

  def add_user
    @document = BaseObject.find(@account, {:id => CGI::unescape(params[:doc_id])})
    @document.add_access_rule(params[:user], params[:role])
    redirect_to :action => :view, :doc_id => @document.id
  end

  def update_user
    @document = BaseObject.find(@account, {:id => CGI::unescape(params[:doc_id])})
    @document.update_access_rule(params[:user], params[:role])
    redirect_to :action => :view, :doc_id => @document.id
  end

  def remove_user
    @document = BaseObject.find(@account, {:id => CGI::unescape(params[:doc_id])})
    @document.remove_access_rule(params[:user])
    redirect_to :action => :view, :doc_id => @document.id
  end

These are pretty straightforward and make use of the related BaseObject functions for managing access rules.

Conclusion
And that’s it! Once you get all of it tied together, you’ll have a working document management system that stores content on Google servers, but is completely managed through your own website.

  • Share/Bookmark

GDocs4Ruby: Access Google Docs API with Ruby

As a follow up to the GCal4Ruby Gem, I’ve release a new Ruby Gem that implements the full feature set of functionality for the Google Documents API (aka, Docs, Doclist).  GDocs4Ruby provides the following features:

  • Create, Update and Delete Google Documents via the API
  • Create, Update and Delete Folders
  • Organize and move documents with folders
  • Search for documents using full text query or by title, or by category
  • Upload files to Google Documents
  • Download exports of files as HTML, PDF, PNG and many more
  • Add or remove collaborators and viewers

Design
The GDocs4Ruby objects are designed with ActiveRecord style interfaces.  The Gem includes the Service class for authenticating with the Google servers.  Documents and folders are represented by objects which are loaded from the service class.  Every document type is a subclass of the BaseObject class.  To create a new document, create a new instance of the type you’d like to save, add content and a title and you’re ready to go!

Installation
You can install the GDocs4Ruby gem from the command line:

> gem install gdocs4ruby

Documentation
Documentation is available at: http://cookingandcoding.com/docs/gdocs4ruby/.  You can read more about the gem, get news, updates and see other examples by visiting http://cookingandcoding.com/gdocs4ruby/.

Basic Usage
Using the GDocs4Ruby gem is simple.
To start, you’ll need to create a Service object and authenticate using a valid Google Documents account:

require 'gdocs4ruby'
service = Service.new()
service.authenticate('username@google.com', 'password')

To get a list of all the documents and folders for this account, you can use:

documents = service.files
folders = service.folders

You can create a document in two ways:

  1. Create a simple document with text content:
    doc = Document.new(service)
    doc.title = 'Test Document'
    doc.content = '<h1>This is HTML</h1>'
    doc.content_type = 'text/html'
    doc.save
    
  2. Create a new document using content from a local file:
    doc = Spreadsheet.new(service)
    doc.title = 'Test Spreadsheeet'
    doc.local_file = '/path/to/some/spreadsheet.xls'
    doc.save
    

To search for existing documents, you can either use the BaseObject#find class method to search all document types, or if you want to narrow your search to a single type, you can use the relevant find method, i.e. Document#find. You can do a full text query, a title query, or simply load a document by its unique ID.

  1. Search only documents for text:
    docs = Document.find(service, 'text to search')
    
  2. Search all documents for a specific title:
    docs = BaseObject.find(service, nil, 'any', {:title => 'title to search'})
    
  3. Find a document by id:
    docs = Document.find(service, {:id => @doc_id})
    

In addition to working with documents, you can also add users as collaborators or viewers.

  1. To add a user as a collaborator:
    doc = Document.find(service, {:id => @doc_id})
    doc.add_access_rule('user@gmail.com', 'writer')
    
  2. To change a user to a viewer:
    doc = Document.find(service, {:id => @doc_id})
    doc.update_access_rule('user@gmail.com', 'reader')
    
  3. To remove a user completely:
    doc = Document.find(service, {:id => @doc_id})
    doc.delete_access_rule('user@gmail.com')
    
  • Share/Bookmark