Archive for the 'ruby' Category

ruby on rails 2.1 upload music and read id3 tags before saving to amazon S3

Sorry it has been so long since my last post. I doubt anyone is reading this anyways. In this article i’m going to attempt to show you what you need to know in order to upload a file (music in this case) into your rails application and safely tuck it away on amazons simple storage solution. I realize this is a very specific application. However, it took me two days to get this working properly so I figured i’d share what I did in order to get this working. I’m assuming you are using rails 2.1 (but that may not be required) and have already created your application. if not you can create and application by issuing the typical rails application_nam command

If you are just planning to upload file to S3 and do not need to manipulate them else do anything to them before they end up on the server. Then I suggest you just use the plugin.

simply install

script/plugin install http://svn.techno-weenie.net/projects/plugins/attachment_fu/

after that just follow the instructions on http://clarkware.com/cgi/blosxom/2007/02/24 you should be up and running in no time with the help of this plugin

Lets assume you don’t want to use the plugin

But why would we not want to use the plugin:?

  1. Way more code than you need for the task at hand
  2. No simple way to manipulate the file or extract data from it before sending to S3

  3. So now that we are going to perform this operation manually there are a few things you will need for this example. You will need to install the mp3info gem as root at command prompt.
    gem install mp3info
    gem install aws-s3
    Say yes to any dependencies once those two gems successfully install you will have to restart your webserver and they will be ready to use in your application

    I’m going to skip over a lot here because I don’t know how you want to configure your application however the steps are pretty simple create some scaffold. Make an upload form.
    and then save the data. I’m only going to show you the model code. I believe everything is self explanatory from there. I will be happy to field any questions if anyone cares to ask. Enought chit chat show me the code

    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
    94
    
    require "ftools"
    require "aws/s3"
    require "mp3info"
    class Song < ActiveRecord::Base
       include AWS::S3
       belongs_to              :user
       has_one                 :song_metadata
       before_validation       :set_all_meta_data
       after_save              :write_file
       after_destroy           :delete_file
       before_create           :connect_to_amazon
       validates_inclusion_of  :mime_type, :in =>%w( audio/mpeg audio/mpg ), :message =>"The file you uploaded is not an mp3"
       validates_inclusion_of  :file_size, :in =>300.kilobytes..20.megabytes, :message=>"must be between 300k and 20mb"
       attr_accessor :id3tags
       def song=(file_data)
         @file_data = file_data
       end
       def write_file
         buckets = Service.buckets
         bucketexists=false
         buckets.each do |bucket|
           if bucket.name == AppConfig.ec2["bucket"]
             bucketexists=true
           end
         end
         if !bucketexists
           Bucket.create(AppConfig.ec2["bucket"])
         end
         @file_data.rewind
         if !S3Object.exists? get_file_url, AppConfig.ec2["bucket"]
          createsong_meta
          S3Object.store(get_file_url, @file_data.read, AppConfig.ec2["bucket"],:access => :public_read)
         end
       end
    def createsong_meta
            m = SongMetadata.new
            self.song_metadata = m
            if id3tags.title
             m.title       = id3tags.title
            else
             m.title = "No song name set"
            end
            if id3tags.artist
              m.artist      = id3tags.artist
            else
              m.artist = "No artist name set"
            end
            if id3tags.album
              m.album = id3tags.album
            else
              m.album = "No album name set"
            end
            m.year        = id3tags.year
            m.track_number= id3tags.tracknum
            m.save!
       end
       def get_song
         self.connect_to_amazon
         S3Object.value mp3_url, AppConfig.ec2["bucket"]
       end
       def delete_file
         self.connect_to_amazon
         S3Object.delete mp3_url, AppConfig.ec2["bucket"]
       end
       def set_all_meta_data
         if @file_data!="" && @file_data
          @file_data.rewind
          self.mime_type = @file_data.content_type
          if self.mime_type == "audio/mpg" || self.mime_type=="audio/mpeg"
            tmplocal = "#{RAILS_ROOT}/tmp/musicfiles"
            tmpname = "#{Time.now}-#{@file_data.original_filename}"
            File.makedirs(tmplocal)
            File.open("#{tmplocal}/#{tmpname}", "w") { |file| file.write(@file_data.read) }
            mymp3 = Mp3Info.open("#{tmplocal}/#{tmpname}")        self.id3tags     = mymp3.tag
            self.bitrate     = mymp3.bitrate
            self.samplerate  = mymp3.samplerate
            self.samplerate  = mymp3.samplerate
            self.mpeg_version= mymp3.mpeg_version
            self.layer       = mymp3.layer
            self.length      = mymp3.length
            self.file_size   = @file_data.size
            mymp3.close
            File.delete("#{tmplocal}/#{tmpname}")
            self.mp3_url = get_file_url
          end
        end
       end
       def get_file_url
        "#{user_id}/xrays/#{id}/song/#{@file_data.original_filename}"
       end
       def connect_to_amazon
         Base.establish_connection!(:access_key_id=> AppConfig.ec2["access_key"], :secret_access_key => AppConfig.ec2["secret_key"])
       end
    end

    That is really the meat and potatoes. It is all hanging out in the model nicely tucked away. getting the data out of S3 is simple and is left as an exercise for the reader. Obviously you need to insert your own access_key and secret key. I’m not making any assumptions about the configuration of your application and thus this is entirely us to you the reader as well. Hope this helps someone..

Highlight selected navigation item in ruby on rails

For an example site where I used this technique please refer to www.howcuteismypet.com you’ll notice that when you click on best / worst or most recent it is highlighted with a black underline. This type of highlighting seems trivial. However to do it in rails in a nice elegant way is not obvious. So without further a do, I present my method. This method dis-involves the controller which makes for a cleaner more centralized place for managing your highlighted navigation.

The primary thing required is a method for creating navigation items. I choose to put my method / helper. in the app/helpers/application_helper.rb that way it will be available to all all of my views. The method I chose to use is

1
2
3
4
5
6
7
8
9
10
11
def nav_selected_no_span(hash_name, selectedkey )
                result =""
                hash_name.each do |item|
                        if(item[0].to_s== selectedkey)
                                result +="<li class="selected"+item[0].to_s.gsub(" ", "_").downcase+" selected">"+link_to(item[0].to_s,item[1].to_s)+"</li>"
                        else
                                result +="<li class=""+item[0].to_s.gsub(" ","_").downcase+" ns">"+link_to(item[0].to_s,item[1].to_s)+"</li>"
                        end
                  end
                  return result
        end

This function is fairly, simple what it allows you to do in your view is, make an unorded list of your navigation items. You would use this method most likely in your app/views/layout/application.rb. An example of the navigation from howcuteismypet.com is:

6
7
8
<ul>
     <%= nav_selected_no_span([["Vote On Pets",home_path],["Best / Worst",best_worst_path],["Most Recent",most_recent_path]], params[:location] ) %>
</ul>

The important thing to notice is the params[:location] the function above says that if params[:location] matches one of the names like “Vote On Pets” then highlight that item. Pretty simple. Then in your css you can define the rules that make something selected. Now you may be wondering where params[:location] is set. And you would be correct to wonder that. That parameter is actually set in your routes file believe it or not. As an example i’ll show you the route for “Vote On Pets”. located in config/routes.rb your could do something like this.

map.home '', :controller => 'index', :action => 'index', :location=>'Vote On Pets'

if you wanted most recent you would do something like this

map.most_recent '/most_recent', :controller=>'index',:action=>'recent',:location=>'Most Recent'

The location parameter is sent to the helper and the correct items is selected. So I hope this helps you in your efforts to stay dry and much more organized.

Questions and comments are very welcome on this blog. We like to know we are not talking to ourselves, so if you would like further explanation please feel free to post a comment.

Ruby on rails yahoo-geocoder

I recently was given a task which required some geocoding. Eventually the geocodes will find there way into a google map. The temptation with google maps is the geocode everything using googles built in geocoding. This is great for a couple of points. However if you are going to have many points you are better off caching the longitude and latitude for speedy marker sets.

in order to get yahoo-geocoder working you’ll want to install the gem

Gem install yahoo-geocode

After that simple step you will want to reboot any mongrel or webrick servers that are running to ensure that the new gem is loaded in your environment.

once installed you need to get a key from yahoo

you should be ready to go now. using the yahoo geocode api to cache latitude and longitude is as simple as this

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
require 'yahoo/geocode'
class Post < ActiveRecord::Base
	before_validation :fillfields
	validates_presence_of :latitude
	validates_presence_of :zipcode, :if=>:in_us?
	validates_presence_of :city
	def in_us?
		country=='US'
	end
	private
	def fillfields
		yg=Yahoo::Geocode.new "Your api id goes here"
		begin
			locations = yg.locate  self.zipcode+', '+self.city+', '+self.country
			self.longitude=locations.first.longitude
			self.latitude =locations.first.latitude
			self.city = locations.first.city
			self.country = locations.first.country
		rescue
			self.errors.add_to_base('Validation could not be done')
		end
	end
end

As you can see everything is nicely tucked away in the posts model. There are no changes to the views or controllers for this one. After that you can call:

Post.find(:all).to_json

or

Post.find(:all).to_xml

to start consuming the locations in google maps or any other mapping software for that matter.