The Artima Developer Community
Sponsored Link

Ruby Buzz Forum
Ruby Duck Typing Goodness

0 replies on 1 page.

Welcome Guest
  Sign In

Go back to the topic listing  Back to Topic List Click to reply to this topic  Reply to this Topic Click to search messages in this forum  Search Forum Click for a threaded view of the topic  Threaded View   
Previous Topic   Next Topic
Flat View: This topic has 0 replies on 1 page
Guy Naor

Posts: 104
Nickname: familyguy
Registered: Mar, 2006

Guy Naor is one of the founders of famundo.com and a long time developer
Ruby Duck Typing Goodness Posted: Jan 12, 2007 7:48 PM
Reply to this message Reply

This post originated from an RSS feed registered with Ruby Buzz by Guy Naor.
Original Post: Ruby Duck Typing Goodness
Feed Title: Famundo - The Dev Blog
Feed URL: http://devblog.famundo.com/xml/rss/feed.xml
Feed Description: A blog describing the development and related technologies involved in creating famundo.com - a family management sytem written using Ruby On Rails and postgres
Latest Ruby Buzz Posts
Latest Ruby Buzz Posts by Guy Naor
Latest Posts From Famundo - The Dev Blog

Advertisement

Coming over from C++ to Ruby, duck typing was one of the really cool features I learned about. You don't use it every day, but when you need it, it's an amazing tool.

Case in point: writing the admin interface to the Famundo help app (you can currently see the public interface at Famundo's Help. (A new version is coming out shortly, and I intend to Open Source it soon.) I used AjaxScaffold for the interface, but wanted to also manage file uploads. I wanted to keep it the same interface as the rest of the tables, as it gives me a nice UI, sorting, searching, etc...

To make that work, I duck typed a class that uses the filesystem, but acts like an ActiveRecord class. I then pointed AjaxScaffold at it and as far as the user experience goes, it's just like managing a database table. Simple and intuitive.

I didn't duck type everything in ActiveRecord, just the stuff that AjaxScaffold needed. Of course, if the need comes, adding more methods is very easy.

Following is the class I created. The $STORAGE_DIR global points to where the storage dir is. Usually something under public so that it's easy to serve the files. But you can also use x-send-file or some other trick and put it someplace else.

class StoredFile 

  attr_accessor :size, :filename, :modified_time

  # Trick the id...
  def id
    filename
  end

  def id=(the_id)
    filename = the_id
  end

  # All the methods we need to overwrite from active_record...
  def self.table_name
    'stored_files'
  end

  def self.primary_key
    'filename'
  end

  def filename_before_type_cast
    filename
  end

  def self.count(*args)
    options = args.last.is_a?(Hash) ? args.pop : {} # Taken from the rails source!
    fltr = options[:conditions] || ''
    Dir.entries($STORAGE_DIR).delete_if{|i| i[0..0] == '.' || (fltr.is_a?(Regexp) ? i !~ fltr : !i.downcase.include?(fltr.to_s))}.size
  end

  def self.find(*args)
    options = args.last.is_a?(Hash) ? args.pop : {} # Taken from the rails source!
    # See if it's a single file find, like ID in rails tables
    if args.first.is_a?(String)
      fname = args.first.gsub(/[^\w\.\-]/, '_')
      raise "File not found #{args.first}" if !File.exist?("#{$STORAGE_DIR}#{fname}")
      return StoredFile.init_from_file(fname)
    end

    fltr = options[:conditions] || ''
    flist = Dir.entries($STORAGE_DIR).delete_if{|i| i[0..0] == '.' || (fltr.is_a?(Regexp) ? i !~ fltr : !i.downcase.include?(fltr.to_s))}.collect{|f| StoredFile.init_from_file f }

    # This is now an unsorted list of files. Now we can sort and apply limits/offsets
    if options[:order]
      options[:order] =~ /^(.+) (asc|desc)?$/i
      reverse_ord = !$2.nil? && ($2.downcase == "desc")
      case $1
        when 'filename'     : flist.sort! {|a,b| a.filename <=> b.filename }
        when 'modified_time': flist.sort! {|a,b| a.modified_time <=> b.modified_time }
        when 'size'         : flist.sort! {|a,b| a.size <=> b.size }
      end

      flist.reverse! if reverse_ord
    end

    idx = (options[:offset] ? options[:offset] : 0).to_i
    len = (options[:limit]  ? options[:limit ] : flist.size).to_i

    flist[idx,len]
  end

  def errors
    []
  end

  def self.destroy fname
    FileUtils.rm_f "#{$STORAGE_DIR}#{fname}"
    fname
  end

  def destroy
    StoredFile.destroy self.id
  end

  protected

  def self.init_from_file f
    fstat = File::stat "#{$STORAGE_DIR}#{f}"
    sf = StoredFile.new
    sf.filename, sf.modified_time, sf.size = f, fstat.mtime, fstat.size
    sf
  end

end

Read: Ruby Duck Typing Goodness

Topic: Meet the Cheat Previous Topic   Next Topic Topic: The Zen of Auto Rspec

Sponsored Links



Google
  Web Artima.com   

Copyright © 1996-2019 Artima, Inc. All Rights Reserved. - Privacy Policy - Terms of Use