Never been to DZone Snippets before?

Snippets is a public source code repository. Easily build up your personal collection of code snippets, categorize them with tags / keywords, and share them with the world

About this user

Sean Cribbs http://seancribbs.com

« Newer Snippets
Older Snippets »
Showing 1-5 of 5 total  RSS 

Make a remote URL work like a file upload (in Rails)

Want to load a remote URL into an acts_as_attachment/attachment_fu model? Use this little utility class.

  class UrlUpload
    EXTENSIONS = {
      "image/jpeg" => ["jpg", "jpeg", "jpe"],
      "image/gif" => ["gif"],
      "image/png" => ["png"]
    }
    attr_reader :original_filename, :attachment_data
    def initialize(url)
      @attachment_data = open(url)
      @original_filename = determine_filename
    end

    # Pass things like size, content_type, path on to the downloaded file
    def method_missing(symbol, *args)
      if self.attachment_data.respond_to? symbol
        self.attachment_data.send symbol, *args
      else
        super
      end
    end
    
    private
      def determine_filename
        # Grab the path - even though it could be a script and not an actual file
        path = self.attachment_data.base_uri.path
        # Get the filename from the path, make it lowercase to handle those
        # crazy Win32 servers with all-caps extensions
        filename = File.basename(path).downcase
        # If the file extension doesn't match the content type, add it to the end, changing any existing .'s to _
        filename = [filename.gsub(/\./, "_"), EXTENSIONS[self.content_type].first].join(".") unless EXTENSIONS[self.content_type].any? {|ext| filename.ends_with?("." + ext) }
        # Return the result
        filename
      end
  end


Now when you have the URL you want to load, do something like this:

@model.uploaded_data = UrlUpload.new(url)


Or better yet, make a pseudo-accessor on your aaa/attachment_fu model so you can stay "model-heavy".

def url=(value)
  self.uploaded_data = UrlUpload.new(value)
end

Compress your ActiveRecord sessions

Using ActiveRecordStore and your sessions are getting too big? Try this!

# in environment.rb or some file you require
require 'zlib'
CGI::Session::ActiveRecordStore::Session.class_eval {
  class << self
    def marshal_with_compression(data)
      Zlib::Deflate.deflate(marshal_without_compression(data))
    end
    def unmarshal_with_compression(data)
      unmarshal_without_compression(Zlib::Inflate.inflate(data))
    end
    alias_method_chain :marshal, :compression
    alias_method_chain :unmarshal, :compression
  end
}

# in migration
def self.up
  change_column :sessions, :data, :binary
end

def self.down
  change_column :sessions, :data, :text
end

Turn CSV with headers into Array of Hashes (in 5 lines or less)

This assumes you have a CSV file whose first line are headings/labels for the individual columns.

require 'csv'

csv_data = CSV.read 'data.csv'
headers = csv_data.shift.map {|i| i.to_s }
string_data = csv_data.map {|row| row.map {|cell| cell.to_s } }
array_of_hashes = string_data.map {|row| Hash[*headers.zip(row).flatten] }

Export Adobe FDF and XFDF from ActiveRecord

I needed to export XFDF from an application. This code is kind of untested, but is based on the solution I came up with. I would appreciate responses/modifications. It's very straightforward mixin stuff, for the most part.
# (X)FDF Export for ActiveRecord
# Based on Justin Koivisto's FDF library for PHP
# Author: Sean Cribbs, seancribbs_AT_gmail_DOT_com, http://seancribbs.com
module FDF
  def self.included(base)
    base.extend ClassMethods
  end
  
  module ClassMethods
    # Options:
    #  <tt>:filename</tt> - The filename of the associated PDF document.  REQUIRED!
    #  <tt>:indentation</tt> - How much to indent the resulting XFDF (XML)
    #  <tt>:include</tt> - Which associated models to include in the generated XFDF.
    #  <tt>:exclude_attributes</tt> - Which attributes of the current model should not be exported.  By default all non-internal attributes are exported (i.e. everything but _id fields).
    #  <tt>:include_attributes</tt> - Which attributes of the current model should be exported in addition to the default. By default all non-internal attributes are exported (i.e. everything but _id fields).
    #  <tt>:attributes</tt> - Override which attributes to export.
    def exports_xfdf(options = {})
      raise ArgumentError, "A :filename option must be specified." unless options[:filename]
      options[:indentation] ||= 2
      options[:include] = options[:include].is_a?(Array) ? options[:include] : [options[:include]].compact
      unless included_modules.include? XFDFMethods
        class_inheritable_accessor :xfdf_options
        extend ClassMethods
        include XFDFMethods
      end
      self.xfdf_options = options
    end
    
    # Options:
    #  <tt>:filename</tt> - The filename of the associated PDF document.  REQUIRED!
    #  <tt>:include</tt> - Which associated models to include in the generated FDF.
    #  <tt>:exclude_attributes</tt> - Which attributes of the current model should not be exported.  By default all non-internal attributes are exported (i.e. everything but _id fields).
    #  <tt>:include_attributes</tt> - Which attributes of the current model should be exported in addition to the default. By default all non-internal attributes are exported (i.e. everything but _id fields).
    #  <tt>:attributes</tt> - Override which attributes to export.
    def exports_fdf(options = {})
      raise ArgumentError, "A :filename option must be specified." unless options[:filename]
      options[:include] = options[:include].is_a?(Array) ? options[:include] : [options[:include]].compact
      unless included_modules.include? FDFMethods
        class_inheritable_accessor :fdf_options
        extend ClassMethods
        include FDFMethods
      end
      self.fdf_options = options
    end
  end

  module XFDFMethods
    def to_xfdf(options = {})
      options.reverse_merge! self.class.xfdf_options
      fields = Util.collect_values(self, self.class.content_columns.map(&:name), options)
      filename = options[:filename]
      xml = Builder::XmlMarkup.new :indentation => options[:indentation]
      xml.instruct!
      xml.xfdf("xmlns" => "http://ns.adobe.com/xfdf/", "xml:space" => "preserve") {
        xml.f :href => filename
        xml.fields {
          fields.each do |field, value|
            xml.field(:name => field) {
              if value.is_a? Array
                  value.each {|item| xml.value(item.to_s) }
              else
                xml.value(value.to_s)
              end
            }
          end
        }
      }
      xml.target!
    end    
  end
  
  module FDFMethods
    def to_fdf(options={})
      options.reverse_merge! self.class.fdf_options
      fields = Util.collect_values(self, self.class.content_columns.map(&:name), options)
      filename = options[:filename]
      data = "%FDF-1.2\n%âã�?Ó\n1 0 obj\n<< \n/FDF << /Fields [ "
      fields.each do |field, value|
        if value.is_a? Array
          data << "<</T(#{field})/V["
          value.each {|v| data << "(#{v.strip})"}
          data << "]>>"
        else
          data << "<</T(#{field})/V(#{value.strip})>>"
        end
      end
    end
    data << "] \n/F (#{filename}) /ID [ <#{MD5.md5(Time.now).to_s}>\n ] >>" <<
            " \n>> \nendobj\ntrailer\n" << "<<\n/Root 1 0 R \n\n>>\n%%EOF\n"
  end
  
  module Util
    def self.collect_values(object, defaults, options = {})
      attrs = []
      if options[:attributes]
        attrs = stringify_all(options[:attributes]) rescue []
      else        
        [:include_attributes, :exclude_attributes].each do |opt|
          options[opt] = stringify_all(options[opt]) rescue []
        end
        attrs = stringify_all(defaults) + options[:include_attributes] - options[:exclude_attributes]
      end
      fields = attrs.inject({}) do |hash, key|
        value = object.send(key) rescue nil
        hash.merge key => value
      end
      fields.merge collect_association_values(object, options)
    end
    
    def self.collect_association_values(object, options = {})
      return {} if options[:include].blank?
      values = {}
      options[:include].each do |association|
        unless object.send(association).blank?
          models = object.send(association)
          unless models.is_a? Array
            columns = models.class.content_columns.map(&:name)
            values.merge! association_dump(association, models, columns)
          else
            models.each_with_index do |model, index|
              columns = model.class.content_columns.map(&:name)
              values.merge! association_dump("#{association.singularize}_#{index+1}", model, columns)
            end
          end
        end
      end
      values
    end
    
    def self.association_dump(prefix, object, attributes)
      attributes.inject({}) do |hash, attr|
        value = object.send(attr) rescue nil
        hash.merge "#{prefix}_#{attr}" => value
      end
    end
    
    def self.stringify_all(ary)
      ary.compact.map(&:to_s).uniq
    end
  end
end
ActiveRecord::Base.send :include, FDF


Also available from: http://pastie.caboo.se/38835

Home-brewed file upload in Ruby on Rails

Most of this comes from others' work, but I was able to tool it to my needs and fix some bugs. All of these lines go in the model, which for me has a :file and :content_type attributes. :file stores the complete path to the uploaded file. Be sure to change the string in path_to_file to the place where you want files stored, and that proper permissions are set on that path. Also, sanitize_filename doesn't HAVE to be a private method -- make it public if you want.

### Model ###
 def file=(uploaded_file)  
    @uploaded_file = uploaded_file
    @filename = sanitize_filename(@uploaded_file.original_filename)
    write_attribute("content_type", @uploaded_file.content_type)
  end
  
  def after_create
    if !File.exists?(File.dirname(path_to_file))
      Dir.mkdir(File.dirname(path_to_file))
    end
    if @uploaded_file.instance_of?(Tempfile)
      FileUtils.copy(@uploaded_file.local_path, path_to_file)
    else
      File.open(self.path_to_file, "wb") { |f| f.write(@uploaded_file.read) }
    end
    write_attribute("file", path_to_file)
  end

  def after_destroy
    if File.exists?(self.file)
      File.delete(self.file)
      Dir.rmdir(File.dirname(self.file))
    end
  end
  
  def path_to_file
    File.expand_path("#{RAILS_ROOT}/upload/#{self.id}/#{@filename}")
  end
  
  private
  def sanitize_filename(file_name)
    # get only the filename, not the whole path (from IE)
    just_filename = File.basename(file_name) 
    # replace all none alphanumeric, underscore or perioids with underscore
    just_filename.gsub(/[^\w\.\_]/,'_') 
  end
  
### View ###
...
<input type="file" name="model[file]" />
...
« Newer Snippets
Older Snippets »
Showing 1-5 of 5 total  RSS