RailsBridge

DESCRIPTION

Easy embedding of remote content into your Rails app, plus exporting of your Rails layouts templatized for other languages (such as PHP).

SYNOPSIS

RailsBridge has two components.

RailsBridge::ContentBridge

Allows for easy embedding of content from remote servers directly into the HTML, XML, or Javascript returned by your Rails application. The remote server may be an external application, a web service, or any other resource available via HTTP on the network.

RailsBridge::LayoutBridge

Allows for easy exporting of your Rails HTML layouts as templates for other applications.


INSTALLATION

rails_bridge is installed as a Ruby gem.

gem install rails_bridge

Include the gem in your Gemfile.

gem 'rails_bridge'

(Note: RailsBridge depends on the Typhoeus gem. See CREDITS for more information about installation requirements for Typhoeus if you have difficulty with installation.)


CONTENT BRIDGE

The content bridge is used to retrieve and cache content from external HTTP servers. This is achieved by defining and executing content requests. Requests are defined using the RailsBridge::ContentBridge class.

Up and Running in 5 minutes

  • Install the gem in your Rails app.

  • Generate a content bridge using the provided Rails generator rails g content_bridge twitter_status_cacher

  • Edit the generated class file (app/rails_bridge/content_bridges/twitter_status_cacher.rb).

The following shows an example.

require 'json'

class TwitterStatusCacher < RailsBridge::ContentBridge
  self.request_timeout = 1000     # miliseconds
  self.cache_timeout   = 60       # seconds
  self.host            = 'api.twitter.com'
  self.path            = '/statuses/user_timeline.json'
  self.default_content = '<<Twitter unavailable>>'
  self.on_success {|content| JSON.parse(content).first["text"]}
end

In your controller:

@soopa_latest_tweet = TwitterStatusCacher.get_remote_content(:params=>{:screen_name => 'soopa'})

That’s it! If the request time’s out before 1 second, “<<Twitter unavailable>>” will be returned instead. If the request succeeds, the content will be cached locally for 60 seconds and returned for each subsequent call to

TwitterStatusCacher.get_remote_content(:params=>{:screen_name => 'soopa'})

There are other more flexible ways to define reusable content requests. Read on for more details.

Defining reusable requests

Reusable requests can be defined on the ContentBridge class:

RailsBridge::ContentBridge.content_request( :server_com ) do |request|
  request.protocol        = 'http'  # can be 'http' or 'https'.  'http' is the default
  request.host            = "server.com"
  request.port            = 8080 # default is 80
  request.path            = "/some/path"
  request.params          = {:param1=>'a value', :param2=>'another value'}  # URL query params
  request.default_content = "Content unavailable at this time."
  request.request_timeout = 1000 # miliseconds
  request.cache_timeout   = 5000 # seconds
end

These requests can then be executed using the automatically generated class method:

content = RailsBridge::ContentBridge.get_server_com

This issues an HTTP GET to “server.com:8080/some/path?param1=a%20value&param2=another%20value” and returns the content. If the server is unavailable or returns an error, or if the request times out, the default content is returned instead.

You can also simply use the url method to define the protocol, host, port, path, and params values of a request. The following declaration is equivalent to the definition above.

RailsBridge::ContentBridge.content_request( :server_com ) do |request|
  request.url = "http://server.com:8080/some/path?p1=value"
  request.default_content = "Content unavailable at this time."
  request.request_timeout = 1000 # miliseconds
  request.cache_timeout   = 5000 # seconds
end

Pre-processing returned content

You may pre-process the content returned by a request by defining an on_success block. The remote content is passed as a parameter and the return value of the block is used in place of the original content.

RailsBridge::ContentBridge.content_request( :server_com ) do |request|
  request.url = "http://server.com:8080/some/path"
  request.params = {:param1=>'a value', :param2=>'another value'}
  request.default_content = "Content unavailable at this time."
  request.request_timeout = 1000 # miliseconds
  request.on_success {|content| JSON.parse(content)}
end

Caching

Content is automatically cached if a cache timeout is defined for the request.

RailsBridge::ContentBridge.content_request( :server_com ) do |request|
  request.url = "http://server.com:8080/some/path"
  request.params = {:param1=>'a value', :param2=>'another value'}
  request.default_content = "Content unavailable at this time."
  request.request_timeout = 1000 # miliseconds
  request.cache_timeout = 3600 # seconds
end

content = RailsBridge::ContentBridge.get_server_com

The content returned from first request will be cached for 3600 seconds. Subsequent requests issued during that time will return the cached content.

To explicitly skip the cache, override the cache_timeout with a value of 0. This will also remove any entry in the cache for this request.

content = RailsBridge::ContentBridge.get_server_com( :cache_timeout=>0 )

The cache key is derived from the complete URL of the request, including query params, so each request with a unique URL is cached independently.

When RailsBridge is loaded in a Rails application, the configured Rails cache is used by default. When loaded outside Rails, ActiveSupport::Cache::MemoryStore is used by default.

One-line requests

If desired, all of the above functionality can be achieved in one line:

content = RailsBridge::ContentBridge.get_remote_content("http://server.com:8080/some/path?param1=a%20value&param2=another%20value", {:cache_timeout=>3600, :default_content=>"Content Unavailable", :request_timeout=>1000} )

Setting defaults for a group of requests.

Default values can be set for a group of requests by sub-classing RailsBridge::ContentBridge and defining them on the class:

class TwitterStatusCacher < RailsBridge::ContentBridge
  self.request_timeout = 500    # miliseconds
  self.cache_timeout   = 60     # seconds
  self.host            = 'api.twitter.com'
  self.path            = '/statuses/user_timeline.json'
  self.on_success {|content| JSON.parse(content)}

  content_request( :hoonpark ) {|r| r.params = {:screen_name => 'hoonpark'}}
  content_request( :soopa ) {|r| r.params = {:screen_name => 'soopa'}}
end

soopa_latest_tweet = TwitterStatusCacher.get_soopa.first
hoonpark_latest_tweet = TwitterStatusCacher.get_hoonpark.first

Setting global defaults

Global default values can be set directly on the RailsBridge::ContentBridge class. They are used by all inheriting sub-classes unless overridden by the sub-class.

RailsBridge::ContentBridge.logger = Logger.new(STDOUT)
RailsBridge::ContentBridge.cache = ActiveSupport::Cache::MemCacheStore(:compress => true)
RailsBridge::ContentBridge.cache_timeout = 5.minutes
RailsBridge::ContentBridge.request_timeout = 500 # miliseconds

I recommend putting these declaration in an initializer file under config/initializers. For example config/initializers/content_bridge.rb.

Overriding request values on execution

All defined values for a request can be specified at runtime by passing them in as options to the get method. All parameters passed this way will override existing values, with the exception of :params, which are merged with those defined at the request declaration and class level.

content = RailsBridge::ContentBridge.get_server_com( :params=>{:p1=>'p1'}, :request_timeout=>2000, :cache_timeout=>0 )

Using the provided Rails generator to create ContentBridge classes.

A generator is included named ‘content_bridge’ that automates generation of content bridge classes in your application.

eg.

rails g content_bridge my_content_bridge my_request

will create a class named MyContentBridge containing a prototype request definition named my_request and place it under _app/rails_bridge/content_bridges.

Batching requests for parallel execution

You can also queue up a batch of multiple requests and execute them in parallel to minimize the time it takes to execute them all. In order to assign the results, you must use the request_<request_name> method and pass a block accepting the result as a parameter which assigns the result to a variable in your context. When you have scheduled all of your requests, call execute_requests to execute them in parallel. When execute_requests returns, all of the blocks passed to your request methods will have been executed.

class TwitterStatusCacher < RailsBridge::ContentBridge
  self.request_timeout = 2000    # miliseconds
  self.cache_timeout   = 60     # seconds
  self.host            = 'api.twitter.com'
  self.path            = '/statuses/user_timeline.json'
  self.on_success {|content| JSON.parse(content).first['text']}

  content_request( :hoonpark ) {|r| r.params = {:screen_name => 'hoonpark'}}
  content_request( :soopa ) {|r| r.params = {:screen_name => 'soopa'}}
end

soopa_latest_tweet = hoonpark_latest_tweet = nil

TwitterStatusCacher.request_soopa do |result|
  soopa_latest_tweet = result
end

TwitterStatusCacher.request_hoonpark do |result|
  hoonpark_latest_tweet = result
end

# soopa_latest_tweet and hoonpark_latest_tweet not assigned yet

TwitterStatusCacher.execute_requests # executes requests in parallel

# soopa_latest_tweet and hoonpark_latest_tweet will now be assigned

Loading your content bridge classes

All classes under app/rails_bridge/content_bridges will be loaded automatically during application initialization and reloaded when in running in development.

LAYOUT BRIDGE

The layout bridge lets you export your Rails application layouts templatized for other languages. A controller is automatically included in your app when the ‘rails_bridge’ gem is installed. You can access it at:

http://app_hostname/rails_bridge/layouts

This will show you all of your applications available layouts. To view a layout without content, click the link of one of the layouts. You will see the entire HTML layout with no content. To insert templating content, implement a view for that layout as app/rails_bridge/layouts/<layout_name>/content.html.erb.

In that view, define content for your content_for’s and provide the template code to be yielded to the main content area of your layout.

Also, any partials that exist under app/rails_bridge/views will take precedence over those in your normal views directory. Take advantage of this to override content for layout partials when rendering your templatized layout.


TODO

  • Create a ContentBridge class method to purge the cache.

  • Put the pre-processor block after the cache get so it can assign to variables on each execution.

  • Create a Rake task to export LayoutBridge layouts from the command line.

  • Add test to verify partial precedence and content precedence for layout bridge.

DEVELOPMENT

RailsBridge was developed and tested using Ruby v1.9.2 and Rails v3.0.3.

You may download the source from Github at github.com/capitalthought/rails_bridge.

You can start an IRB session with the RailsBridge code loaded and initialized by running:

script/console

TESTS

RSpec 2.x is used for the Rails Bridge test suite. To run the suite:

rake

or

rake spec

NOTE: The test suite has an additional dependency on the eventmachine gem to to implement a test HTTP server.


CREDITS

The ContentBridge component of RailsBridge is fundamentally a wrapper around the excellent Typhoeus gem; packaged as a Rails engine with conveniences added for managing content in the context of Rails. Typhoeus, in turn, depends on a native installation of curl.

Typhoeus usually installs nicely if you already have curl installed on your OS. If you have difficulty installing Typhoeus, please refer to the following sources.

LICENSE

Copyright 2010 Capital Thought, LLC

Licensed under the Apache License, Version 2.0 (the "License");
you may not use any part of this software or its source code except 
in compliance with the License.  You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.