ActiveResource and YouTube


This article is about consuming YouTube API in your Ruby/Rails project using ActiveResource. Moreover, this article is an example of how to extend ActiveResource to consume non rest-style API.

Benefits of using our extension to ActiveResource :

  1. ActiveResource provides a ActiveRecord style interface.
  2. You can modify our extension according to your interface requirement.
  3. No not need to use and rely on Ruby library for YouTube REST API.

YouTube references

Note : The extension is using ActiveResource 2.0.2

ActiveResource extension

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78


require 'rubygems'
require 'activeresource'
    
class ActiveYouTube < ActiveResource::Base
  class << self
  ## Remove format from the url.
  def element_path(id, prefix_options = {}, query_options = nil)
    prefix_options, query_options = split_options(prefix_options) if query_options.nil?
    "#{prefix(prefix_options)}#{collection_name}/#{id}#{query_string(query_options)}"
  end
    
  ## Remove format from the url.
  def collection_path(prefix_options = {}, query_options = nil)
    prefix_options, query_options = split_options(prefix_options) if query_options.nil?
    "#{prefix(prefix_options)}#{collection_name}#{query_string(query_options)}"
  end

  ## For a collection call, ActiveResource formatting is not 
  ## compliant with YouTube's output.
  def instantiate_collection(collection, prefix_options = {})
    unless collection.kind_of? Array
      [instantiate_record(collection, prefix_options)]
    else
      collection.collect! { |record| instantiate_record(record, prefix_options) }
    end
  end

  ## To convert output into proper standard.
  ## If single element is present in output then entry is not an array.
  ## So this method will ensure entry is always an array.
  alias :old_find :find
  def find(*args)
    output=old_find(*args)
    if output.respond_to?:entry and !(output.entry.kind_of? Array)
      output.entry=[output.entry]
    end
    output
  end

  ## When using ActiveResource::CustomMethods, ActiveResource first tries to retrieve the id using find() 
  ## and then makes a get() call using that id. 
  ## But, youtube returns the url of this item as id, which we don't want. This method overrides the behavior.
  ## Example: comments = Video.find_custom("ZTUVgYoeN_o").get(:comments)
  def find_custom(arg)
    object = self.new
    object.id = arg
    object
  end

  ##  Following method from ActiveResource::CustomMethods extends the capabilities of activeresource for non-standard urls ;-)
  ##  The objects returned from this method are not automatically converted into ActiveResource instances - they are ordinary Hashes. 
  ##  Modifications below ensures that you get ActiveResource instances.
  def get(method_name, options = {})
    object_array = connection.get(custom_method_collection_url(method_name, options), headers)
    if object_array.class.to_s=="Array"
      object_array.collect! {|record| self.class.new.load(record)}
    else
      self.class.new.load(object_array)
    end
  end
  end

  ## Instance Methods: (modifying the ActiveRecord::CustomMethods).
 
  ## This modification is same as defined in above method 
  def get(method_name, options = {})
    self.class.new.load(connection.get(custom_method_element_url(method_name, options), self.class.headers))
  end

  ## Modifying the url formation to make it Youtube API complaint
  def custom_method_element_url(method_name, options = {})    
    "#{self.class.prefix(prefix_options)}#{self.class.collection_name}/#{id}/" +
    "#{method_name}#{self.class.send!(:query_string, options)}"
  end
end

Points to keep in mind
- When using the ActiveResource::CustomMethods (get/post) make sure you are using "find_custom" and not "find"
- Disable the logging of ActiveResource, since the following line leads to exception if logger is enabled.
1
2
3

## line 111, connection.rb
logger.info "--> #{result.code} #{result.message} (#{result.body ? result.body : 0}b %.2fs)" % time if logger

Example usage

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93


#### Create classes for YouTube resources.
class Video < ActiveYouTube
  self.site = "http://gdata.youtube.com/feeds/api"

  ## To search by categories and tags
  def self.search_by_tags (*options)
    from_urls = []
    if options.last.is_a? Hash
      excludes = options.slice!(options.length-1)
      if excludes[:exclude].kind_of? Array
        from_urls << excludes[:exclude].map{|keyword| "-"+keyword}.join("/")
      else
        from_urls << "-"+excludes[:exclude]
      end
    end
    from_urls << options.find_all{|keyword| keyword =~ /^[a-z]/}.join("/")
    from_urls << options.find_all{|category| category =~ /^[A-Z]/}.join("%7C")
    from_urls.delete_if {|x| x.empty?}
    self.find(:all,:from=>"/feeds/api/videos/-/"+from_urls.reverse.join("/"))
  end
end

class User < ActiveYouTube
  self.site = "http://gdata.youtube.com/feeds/api"
end

class Standardfeed < ActiveYouTube
  self.site = "http://gdata.youtube.com/feeds/api"
end

class Playlist < ActiveYouTube
  self.site = "http://gdata.youtube.com/feeds/api"
end



##### Examples #######

#### VIDEO
  ## search for videos
  search = Video.find(:first, :params => {:vq => 'ruby', :"max-results" => '5'})
  puts search.entry.length

  ## video information of id = ZTUVgYoeN_o
  vid = Video.find("ZTUVgYoeN_o")
  puts vid.group.content[0].url

  ## video comments
  comments = Video.find_custom("ZTUVgYoeN_o").get(:comments)
  puts comments.entry[0].link[2].href

  ## searching with category/tags
  results = Video.search_by_tags("Comedy")
  puts results[0].entry[0].title
  # more examples:
  # Video.search_by_tags("Comedy", "dog")
  # Video.search_by_tags("News","Sports","football", :exclude=>["Comedy","soccer"])
  # Video.search_by_tags("News","Sports","football", :exclude=>"soccer")


#### STANDARDFEED
  ## retrieving standard feeds
  most_viewed = Standardfeed.find(:most_viewed, :params => {:time => 'today'})
  puts most_viewed.entry[0].group.content[0].url

#### USER
  ## user's profile - guthrie
  user_profile = User.find("guthrie")
  puts user_profile.link[1].href

  ## user's playlist - john
  user_playlist = User.find_custom("john").get(:playlists)
  puts user_playlist.link[1].href

  ## user's upload or favorites
  rick_video = User.find_custom("rick").get(:uploads)
  puts rick_video.entry[0].group.content[0].url

  ## user's subscription
  user_subscriptions = User.find_custom("guthrie").get(:subscriptions)
  puts user_subscriptions.to_yaml

#### PLAYLIST
  ## get playlist - multiple elements in playlist
  playlist = Playlist.find("EBF5D6DC4589D7B7")
  puts playlist.entry[0].group.content[0].url

  ## get playlist - single element in playlist
  playlist = Playlist.find("45C563323B344971")
  puts playlist.entry[0].group.content[0].url

Filed in ruby
Tagged as   
Posted on 15 January
10 comment Bookmark   AddThis Social Bookmark Button Updated on 23 February
Comments

Leave a response

  1. Edgar J. SuárezJanuary 25, 2008 @ 09:46 PM

    This is awsome, kinda hard for me to fully understand it at a first approach. But I just tested this and wow, my head is full of ideas now that it’s hard to sort them xD.

    Very nice, really.

  2. Josh NicholsJanuary 26, 2008 @ 04:27 AM

    This looks pretty cool. You might want to consider bundling this up as a gem, to more easily share the love.

  3. QuarkJanuary 26, 2008 @ 08:23 PM

    Hi Josh,

    Hmnnn… it just didn’t strike to me that it could be turned into a gem. Surely will look into it.

    Thanks!

  4. BrianFebruary 09, 2008 @ 07:16 PM

    This looks promising, but I’m still trying to get it to work. I made an activeyoutube.rb file, but where do I place it in my rails directory so that rails finds the class ActiveYouTube. Thanks!

  5. QuarkFebruary 09, 2008 @ 08:06 PM

    @Brian,

    If you are getting “load_missing_constant” error. Please rename activeyoutube.rb file to active_you_tube.rb, and it should work.

    Also, If you can wait for a day or two, we are planning to release this as a gem.

  6. ericMarch 24, 2008 @ 09:31 PM

    Hi

    How do I use it? require ‘active_youtube’? That doesn’t seem to work.

  7. QuarksMarch 25, 2008 @ 06:42 AM

    @eric:

    Please refer http://www.quarkruby.com/2008/2/12/active-youtube to use this as gem.

  8. Bestor FreemanJuly 09, 2008 @ 10:55 PM

    Pretty good tut, really helpful, i have to give you credit, you just gave me tons of ideas! I’ll be trying it tonight. Thanks a lot, not having to rely on Ruby library will help me in what i want to do!

  9. Kenan HudabiAugust 01, 2008 @ 09:43 AM

    Thank you man. This nice extension. I used

  10. SaurabhAugust 30, 2008 @ 12:17 PM

    Hi There,

    How do we use Youtube API to submit the videos to youtube through our forms

    Thanks

    Saurabh