Glass-Rails

A DSL for building Google Glass apps using Ruby on Rails.

View project onGitHub

Glass-Rails

This is a ruby gem extracted out of Thirst Droplet, a google glass application. The goal of this gem is to provide a more convenient wrapper around the official Google Api Client for Ruby. This gem is still quite young, and in development, so keep posted for updates (and we also <3 pull requests).

Installation

Add the following to your Gemfile and then run bundle install.

gem 'google-api-client' # this is a dependency
gem 'glass-rails'

Or you can install the gem locally, by running the following command in the terminal:

$ gem install glass-rails

Basic Setup

In order to get this gem setup for use, we require that you have some sort of user model already in place. You can, for example, have a devise generated User model, or a custom hand-rolled user model.

For convenience, this gem provides a command to scaffold some necessary files for glass development:

$ rails g glass:install <optional_name_of_user_model>
                       # defaults to 'User'

If the name of user model is not User then you must specify this when invoking the glass:install command:

$ rails g glass:install person

Files created by the glass:install generator

  1. A config/google-api-keys.yml file for your client_id and client_secret api keys for accessing the mirror api.
  2. A Glass::Rails initializer file in config/initializers/glass.rb for setting up default view paths for your glass templates.
  3. Generates a app/models/google_account.rb model which is automatically associated to your user model.
  4. A controller is created for 'listening' to subscription notifications from your glass users.

Pre-defined Templates for Glass

This gem also includes pre-defined erb templates for use with timeline items. You can generate these templates for use by running the following command:

$ rails g glass:templates

This will generate a name-spaced folder in your app/views directory called glass and populate it with a few useful predefined templates.

Creating Timeline Items

This gem also includes a generator for creating an ActiveRecord backed timeline item or a "glass model".

Glass::TimelineItem (a superclass which you'll be inheriting from)

The Glass::TimelineItem class is a subclass of ActiveRecord which is, for all intents and purposes, an abstract class. We've basically added a few convenience methods to ActiveRecord which allows for the easy population of custom menu items and some procedures for serializing these items for posting them to the glass timeline.

The TimelineItem Generator

You can generate a timeline_item model by executing the following command:

$ rails g glass:model <name of model>

So, for the sake of illustration, let's say you wanted to generate a tweet model, which you intend to use to post particular tweets to a glass user's timeline. I would, then, execute this command:

$ rails g glass:model tweet

This would create a model for me in the following directory app/models/glass/tweet.rb. This Glass::Tweet model would inherit from Glass::TimelineItem and would therefore be accompanied by a sugary goodness which makes posting to glass timelines a ton easier.

Setting up a Glass Model


Defining a Default Template

You may want to specify a default template to use for a given timeline item. For example, if I had two timeline item models, Glass::Tweet and another Glass::BasketballScore, then I could specify a default template for the tweet model to use by using a helper_method in the class definition, like so:

class Glass::Tweet < Glass::TimelineItem
  defaults_template with: "tweet.html.erb" ## this path is relative to 
                                           ## your glass_templates_path, 
                                           ## which you can set in your
                                           ## `config/initializers/glass.rb` 
                                           ## file.

end

and then you could define another default template for your basketball score model like so:

class Glass::BasketballScore < Glass::TimelineItem
  defaults_template with: "two_column.html.erb" ## since this would make
                                                ## sense if wanted to show 
                                                ## a score in each column.
end

Defining Menu Items on the Timeline Item

This gem provides some meta-programming magic to ease the creation of both custom and built-in menu-items for glass, by providing an easy to use DSL for specifying custom and built-in menu-items that a timeline-item should have..

For example, let's say I want to have two default actions on my Glass::Tweet object: 1) email this tweet to me. 2) read the tweet to me aloud.

Inside my Glass::Tweet class definition, I would specify these actions like so:

class Glass::Tweet < Glass::TimelineItem
  has_menu_item :email, icon_url: "http://www.example.com/example-email-icon.png",
                        display_name: "Email", # this is what is shown 
                                               # as text for the menu item
                        handles_with: :email_callback 
                                               # we'll ignore this for now.
  has_menu_item :read_aloud ## this is a built-in action.

end

This would basically define two actions for all items posted with this glass model, i.e. Email & Read Aloud.

You can define a different set of menu items for each model, so that would probably be something to consider when architecting your glass app.

Inserting a Timeline Item to Glass

For the sake of simplicity, let's continue with the example we've been using so far, the Glass::Tweet model. If we had a hypothetical tweet:

@tweet = { 
  content: "Rails is omakase", 
  author: "@dhh" 
}

If we wanted to post it to a glass user's timeline, we would first initialize a Glass::Tweet object and associate it to the google account that we want to send this tweet.

@tweet_timeline_object = Glass::Tweet.new(google_account_id: GoogleAccount.first.id)

Then, we can serialize our html for posting by executing this command:

@tweet_timeline_object.serialize(template_variables: {tweet_content: @tweet[:content], 
                                                      author: @tweet[:author]})

### if you pass in a key named "template_name" 
### into the single argument that `serialize`
### takes as a method, then you can also 
### override the default template rendered for 
### instances of this class.

### For example:
@tweet_timeline_object.serialize(template_variables: {tweet_content: @tweet[:content], 
                                                      author: @tweet[:author]},
                                 template_name: "dramatic-tweet.html.erb")
### will render this tweet into the template located 
### in `app/views/glass/dramatic-tweet.html.erb` 
### provided you haven't overridden glass_templates_path
### in your glass initializer.

How Serialize connects with our Glass Template

When we invoke the serialize method on glass timeline item object, we will populate your template, which should be either the default template for your timeline item class or a manually specified template in the serialize method, with instance variables, each of which correspond to key-value pairs contained within template_variables.

So for our example above, this would mean:

### since we passed this hash:
### {template_variables: {tweet_content: "Rails is omakase",
###                       author: "@dhh"}}
### we will populate our template with the following variables:

@tweet_content = "Rails is omakase"
@author = "@dhh"
### note: the values passed into the serialize method 
###       do not have to be simple data types, they
###       can be full-fledged objects too.

so given an erb template that looks like this:

<article>
  <section><%= @tweet_content %></section>
  <footer><%= @author %></footer>
</article>

We would be creating a html serialization of your data, which would look like this:

<article>
  <section>Rails is omakase</section>
  <footer>@dhh</footer>
</article>

Note: If you want to inspect that your html is getting serialized properly, you can inspect the html yourself by calling to_json on your timeline_item object. This will return a ruby hash which has the html serialization as well as the array of menu items which you intend to post with your timeline item.

And finally, to post to our timeline, we can call this command:

@tweet_timeline_object.insert()

Subscriptions / Notifications

The mirror api lets you subscribe to notifications on Timeline Items. For each user you want to subscribe to notifications for, you must register a callback url with that user's credentials. When a user does an action (delete, custom action, reply, etc.) on a timeline item, Google will post back to that callback url.

A subscription to Google has 5 main parts

  • callbackUrl: Note that this must be HTTPS, so it won't work in development mode.
  • collection: This can be 'timeline' or 'location'
  • verifyToken: This is optional, but lets you verify that the request came from Google. Out of the box, we have set GoogleAccounts to have a randomly generated field called verification_secret that we automatically set as the verifyToken
  • userToken: This is an optional string that Google will always pass back for a particular user. We have set this to be GoogleAccount.id
  • operations: A list of action types (Insert, Update, Delete) you would want to be notified about

Subscriptions was probably the hardest part to test, but we've included some tools that should make it easier to deal with them.

A GoogleAccount, on creation, will subscribe to the Google callback service with the route glass_notifications_callback_url, which we've defined in the Glass installer. The Glass Notifications controller will then handle Google's post and call the appropriate methods within the TimelineItem.

For example, if a glass user activates the menu-item email, then our glass app would get pinged on our subscription notification callback url. From here, we've implemented a callback handler which allows you to easily manipulate any callback logic, in the comfort of your timeline item model.

  has_menu_item :email,
       icon_url: "http://example.com/test.jpg"
       display_name: "Email This!"
       handles_with: :email_callback_method

  def email_callback_method
    # Email this timeline item content to someone
  end

The method email_callback_method gets executed whenever your subscription notification callback url is pinged with the corresponding menu-item event.

You can also add Google's built in menu items:

  has_menu_item :read_aloud

Note that most of the built in timeline actions (but not all) do not trigger callbacks from Google. Exceptions include reply, delete, and share.


Plans for the Future

This framework is still young and being actively developed. We <3 bug reports and pull requests.

To-dos

  1. Work out a wrapper for the mirror location api
  2. Handle all built-in menu-items properly
  3. Figure something out for attachments

Copyright / License

Copyright (c) 2013 Thirst Labs, Inc.

MIT License

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Authors:
Kunal Modi & Han Kang
(of Thirst Labs, Inc.)