1. Skip to navigation
  2. Skip to content

The ELC Community Blog

A knowledge exchange on Ruby on Rails and Agile Development

May 30, 2008

by Dylan Stamat

ImageVoodoo File Extensions

Sparing the details, a project of ours uses a custom built content processor, and not AttachmentFu. It works wonderfully, but I ran into some caveats in regard to how ImageVoodoo handles images. After some digging, I found an interesting post by Nick, which actually touches on my problem indirectly.

In an nutshell, ImageScience allows the loading of extension-less images, which ImageVoodoo does not, ie:

   1  >> ImageScience.with_image("/Users/dstamat/5a1e81c76e634dfc5005db0b1fdf5c58_CGI.11279.6") {}
   2  => nil
   3  >> ImageVoodoo.with_image("/Users/dstamat/5a1e81c76e634dfc5005db0b1fdf5c58_CGI.11279.6") {} 
   4  TypeError: unrecognized format for /Users/dstamat/5a1e81c76e634dfc5005db0b1fdf5c58_CGI.11279.6
   5  	from /Users/dstamat/src/work/.../vendor/gems/image_voodoo-0.2/lib/image_voodoo.rb:180:in `with_image'
   6  	from (irb):8:in `signal_status'

The "solution" in terms of getting this to work properly is to override Tempfile#make_tmpname to allow the slipping in of the file extension, as seen in Nick's post.

To make the libraries consistent however, that's TBD. Each library obviously uses different means for introspection. ImageScience uses FreeImage_GetFIFFromFilename and FreeImage_FIFSupportsReading to determine compatibility, while ImageVoodoo uses Java's ImageIO.getImageReadersBySuffix and writes with ImageIO.write, which requires a format (and will consequently write out the file but not stream).

Will hack up a patch if time permits... but, this will hopefully shed some light for those running into this problem as well :)

May 20, 2008

by Ryan Garver

Dataportability: XRDS-Simple

I've been getting very excited about the Dataportability project (DP) for quite a while now. Their mission is: to promote the idea that individuals have control over their data by determing how they can use it and who can use it. This includes access to data that is under the control of another entity. It's a very cool idea that is gaining a lot of support in very high places. So far companies like MySpace, Google, Microsoft, and Facebook have openly announce their support of DP and its proposed mission. With that kind of weight (those were only a small sample of the companies backing DP) a lot of things can get done very quickly... or very slowly as the case may be. Fortunately the DP group has kept itself relatively independent from the commercial sponsors that have pledged themselves. In fact most of the literature on the DP website doesn't even mention these sponsors as contributing to the standards. Lets hope they can continue to use this autonomy to the advantage of us all.

Among other technologies and standards currently under development, DP leverages OpenID, OAuth, and a number of Microformats (e.g.: hCard, XFN), as well as FOAF. I think it's important to increase awareness of these DP technologies and so I'm going to start putting together some posts to dig in to what they are, why they were created or chosen, and how they work. To start I want to explore a relatively new addition to the DP family that hasn't really received much publicity so far: XRDS-Simple.

XRDS-Simple is the standard that the DP group is developing to solve the problem of service discovery. That is, a standard protocol and format for sharing what services a user uses, for what purpose (to share video or photos, to broadcast updates, to store contacts), and with what priority. This is really important for situations like showing a photo gallery on a users profile page. Where does the user keep their photos? Flickr? Photobucket? Picasa Web Albums?

An Alternative: The rel="me" microformat

There are some other alternatives, however the DP group was concerned that these standards were either too heavy or under powered for the full extend of the task. One group that has been involved with the DP group since the beginning is the microformats group (µf). They have a µf that nearly satisfies the need for a discovery/directory system for services. The spec is called rel="me". This µf links resources to individuals by marking them as relevant to their profile. This is a very barebones approach, but it is also non-intrusive, as with all µfs. These links can me casually scattered within a person's profile page without impacting the normal formatting. But, with µf aware browsers the information gains meaning within the context.

For the larger goals of the DP project it seems that while rel="me" was driving the right road, it didn't take us far enough. Because if the intentional simplicity of the µf features like purpose of a linked service, the local usernames and IDs for using the service, or the service priority compared to similar services couldn't be described.

So how does it work?

XRDS-Simple is a reduced version of the XRDS standard which was developed by OASIS in conversation with the OpenID community. If you have done any work with OpenID you may recognize XRDS as the format used by the Yadis protocol. Here is a sample XRDS-Simple file (taken from the XRDS-Simple 1.0 Draft 1)

   1  <xrds xmlns="xri://$xrds">
   2      <xrd version="2.0" xmlns:simple="http://xrds-simple.net/core/1.0" xmlns="xri://$XRD*($v*2.0)">
   3          <type>xri://$xrds*simple</type>
   4          <service priority="10">
   5            <type>http://specs.example.com/wish_list/1.0</type>
   6            <uri simple:httpmethod="GET">http://books.example.com/wishlist</uri>
   7            <localid>jane</localid><localid>
   8          </localid></service>
   9          <service priority="20">
  10            <type>http://specs.example.com/wish_list/1.0</type>
  11            <uri priority="10" simple:httpmethod="GET">https://dvds.example.org/lists/wishes</uri>
  12            <uri priority="20" simple:httpmethod="GET">http://dvds.example.org/lists/wishes</uri>
  13            <localid>janedoe</localid><localid>
  14          </localid></service>
  15      </xrd>
  16  </xrds>

The XRDS-S lists off a collection of services that are described by the sub-element Type. Each Service element is prioritized and within the service a collection of URIs are prioritized. As you can see, the second service has two URIs and prefers the HTTPS one over the non-SSL URI. The last element in each Service element is a LocalID. The LocalID specifies basically a username or some other identifier that the service will tie to the correct user.

I got a little excited about this and decided to do a quick refresher on my Hpricot skills. I threw together a XRDS-Simple parser that returns a hash of XRDs indexed by id if you have a fragment to work with (see the spec on how this works). Each XRD is a hash of services indexed by the Service > Type. Each Service is an array of URIs which are ordered by overall priority.

   1  xml = Hpricot::XML(str)
   2  xrds = {}
   3  (xml/'XRD').each do |xrd|
   4    if xrd.attributes['xmlns'] == 'xri://$XRD*($v*2.0)' && 
   5        xrd.attributes['version'] == '2.0' && 
   6        (xrd%'Type').inner_text == 'xri://$xrds*simple'
   7  
   8      id = xrd.attributes['id'] || xrds.size
   9      xrds[id] = {}
  10      (xrd/'Service').each do |service|
  11        xrds[id][(service/'Type').inner_text] ||= []
  12        xrds[id][(service/'Type').inner_text] << [service.attributes['priority'].to_i, (service/'URI').map do |uri|
  13          {
  14            :method => (uri.attributes.find{|(k,v)| k =~ /httpMethod/}.last),
  15            :priority => uri.attributes['priority'].to_i,
  16            :local_id => (service%'LocalID').inner_text,
  17            :uri => uri.inner_text
  18          }
  19        end.sort{|l,r| l[:priority]<=>r[:priority]}]
  20      end
  21      xrds[id].each_key do |key|
  22        xrds[id][key] = xrds[id][key].sort{|l,r| l.first<=>r.first}.map{|e| e.last}.flatten.map{|e| e.delete(:priority);e}
  23      end
  24    end
  25  end

If we run this on the above xml and take a look at xrds we will see:

   1  {0=>
   2    {"http://specs.example.com/wish_list/1.0"=>
   3      [{:local_id=>"jane",
   4        :method=>"GET",
   5        :uri=>"http://books.example.com/wishlist"},
   6       {:local_id=>"janedoe",
   7        :method=>"GET",
   8        :uri=>"https://dvds.example.org/lists/wishes"},
   9       {:local_id=>"janedoe",
  10        :method=>"GET",
  11        :uri=>"http://dvds.example.org/lists/wishes"}]}}

May 14, 2008

by Ryan Garver

Defensio Lite

This is a quick post, but I wanted to point out that our new commenting system is now using Defensio spam filtering! This is good because after a day of watching the commenting statistics its pretty clear that we would have been consumed by a porn site or something by the end of the week. The code that we used for this is super simple (possibly too simple) and duplicates some work already done by the talented Marc-André. Oh well. Sometimes you just need to do it yourself. Below is my Defensio API.

   1  class Defensio
   2    cattr_accessor :format
   3    self.format = :xml
   4    
   5    cattr_accessor :service_type
   6    self.service_type = :app # Can be :blog
   7    
   8    cattr_accessor :api_version
   9    self.api_version = '1.2'
  10    
  11    cattr_accessor :api_key
  12    cattr_accessor :owner_url
  13    
  14    def self.configure(confhash)
  15      if confhash['test']
  16        @mock = true
  17        self.owner_url = 'http://www.example.com'
  18        return
  19      else
  20        confhash.each do |prop, val|
  21          self.send("#{prop}=", val)
  22        end
  23      end
  24    end
  25    
  26    def self.method_missing(name, *args)
  27      self.post(name.to_s.dasherize, *args)
  28    end
  29    
  30    private
  31      def self.connection
  32        uri = URI.parse('http://api.defensio.com/')
  33        Net::HTTP.start(uri.host, uri.port)
  34      end
  35    
  36      def self.post(action, params = {})
  37        resp = connection.post(real_path(action), params_from_hash(params))
  38        raise "Problem with request: #{action}" unless resp.code == '200'
  39        parse_response(resp.body)
  40      end
  41    
  42      def self.real_path(action)
  43        "/#{service_type}/#{api_version}/#{action}/#{api_key}.#{format}"
  44      end
  45    
  46      def self.params_from_hash(params = {})
  47        # Thanks Net::HTTPHeader
  48        params.stringify_keys.merge('owner-url' => owner_url).map {|k,v| "#{CGI.escape(k.dasherize.to_s)}=#{CGI.escape(v.to_s)}" }.join('&') 
  49      end
  50    
  51      def self.parse_response(body)
  52        case format
  53        when :yaml
  54          YAML.load(body)
  55        when :xml
  56          Hash.from_xml(body)
  57        end
  58      end
  59  end

I clearly didn't spend much time polishing this, but the usage is a pretty straight forward mapping from the API docs. So to announce an article I call:

   1  Defensio.announce_article(:article_author => 'Ryan Garver', :article_author_email => 'rgarver@domain.com', :article_title => 'Defensio Lite', ... )

There are also some site wide values that are set in a yml file. I'll close with an example.

   1  development:
   2    api_key: a09f87a09f87a098f7a098f7a098f7a0
   3    owner_url: http://www.example.com
   4  
   5  staging:
   6    api_key: 12f3412f341f234f123f4123f412f34f
   7    owner_url: http://mystaging_blog.com
   8  
   9  production:
  10    api_key: 123f412f412f412f3412f3412f3412f3
  11    owner_url: http://elctech.com
  12  
  13  test:
  14    test: true


home | services | Ruby on Rails Development | code | blog | company