Cory O'Daniel – These are just words Software development, thoughts, and randomness

7Jul/100

Testing if something is ‘true’ to all objects in an array/collection in ruby

Maybe there is something that does this already. I didnt know, so I whipped it together really quick. Its an extension to the array class that lets you know if something is 'true' to all objects in the array.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Array
 
  def all_true?(&block)
    t_ref = true  
 
    if block_given?
      self.each do |_val_|
        t_ref &= !!(yield(_val_))
      end
    else
      self.each do |_val_|
        t_ref &= !!(_val_)
      end
    end
 
    t_ref
  end
 
end

You can use / test it like so:

1
2
3
4
5
6
[ true, true, true ].all_true?  #=> true
[ true, true, false ].all_true?  #=> false
 
# Want to run some other non boolean logic on it?
[ 1, 2, 3, 4, 5].all_true?{ |val| val.integer? } #=> true
[ 1, 'a', 3, 4, 6].all_true?{ |val| val.integer? } #=> false

Yeah, I just need to know is shits the same across the whole collection of active record objects sometimes. Now I can do it, go data.

Post to Twitter Post to Digg Post to Facebook Post to Reddit Post to StumbleUpon

5Mar/102

Attaching local or remote files to Paperclip and Milton Models in Rails (Mocking content_type and original_filename in a Tempfile)

I was working on a project today where I needed to import some data from MySpace accounts (yeah, MySpace), which included importing the users profile image. In the controller that did the importing I was using OpenURI to retrieve the image and then turn it into a Tempfile to be attached the the model, like so:

1
2
3
4
5
6
7
def import
#...snip
  tempfile = Tempfile.new( my_filename)
  tempfile.write open( image_url ).read
  @imported_user.images.create(:file => tempfile)
#...snip
end

This doesn't work. It blows up missing one of two methods:

  1. #original_filename
  2. #content_type

If you inspect a normal file upload in Rails which has these methods, you'll find that is just a regular old Tempfile. But it has the methods! If you create a Tempfile manually, it won't have the methods. That's because Rails magics them on. I am not sure why they don't create a subclass like Rails::Tempfile that contains these methods and just use that. I guess its because OOP is retarded (sarcasm).

So, I wrote a little subclass that will take a file path, and quack like the magic'd rails Tempfiles you get from an upload so you can attach local files to models or even remote files.

This works pretty straightforward and I'm currently using it in production. It won't work on windows systems because the dependency on the 'file' binary. Also, some linux systems are missing this library by default, so make sure you yum|dpkg|apt|port or whatever to get it installed.

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
require 'open-uri'
require 'digest/sha1'
 
class RemoteFile < ::Tempfile
 
  def initialize(path, tmpdir = Dir::tmpdir)
    @original_filename  = File.basename(path)
    @remote_path        = path
 
    super Digest::SHA1.hexdigest(path), tmpdir
    fetch
  end
 
  def fetch
    string_io = OpenURI.send(:open, @remote_path)
    self.write string_io.read
    self.rewind
    self
  end
 
  def original_filename
    @original_filename
  end
 
  def content_type
    mime = `file --mime -br #{self.path}`.strip
    mime = mime.gsub(/^.*: */,"")
    mime = mime.gsub(/;.*$/,"")
    mime = mime.gsub(/,.*$/,"")
    mime
  end
end

Usage is pretty simple:

1
2
3
remote_file = RemoteFile.new("http://www.google.com/intl/en_ALL/images/logo.gif")
remote_file.original_filename #=> logo.gif
remote_file.content_type #= image/gif

Using it in your controller:

1
2
3
4
5
def import
  #...snip
  @imported_user.images.create(:file => RemoteFile.new( url_to_image ))
  #...snip
end

Post to Twitter Post to Digg Post to Facebook Post to Reddit Post to StumbleUpon

10Feb/101

Expiring rails fragement caches using an expires time instead of a sweeper (expires_in) – (a ‘duh’ post)

So, I'm setting up fragment caching for some stuff, and I honestly don't care about setting up an observer to sweep some stuff. I just want to cache this one slow fragment for like 5 minutes. I've seen these tutorials all over the web that shows people using the expires_in setting to auto-expire cache like so:

1
Rails.cache.write('test_key', 'test_value', :expires_in => 5.minutes)

That's great and all, but how the hell do I use it for a Page, Action or Fragment cache? Well you just pass the same :expires_in param and yay it works as long as its a mem_cache_store (although, I did see a hackaroo for file_store)

Even the Rails Guide on caching mentions 'expires_in' but doesn't show how to use it.

1
2
3
class StupidController < ApplicationController
  caches_page :whatever, :expires_in => 5.minutes
end

What really got me was when I was trying to use it with a fragment cache. I tried this and it didn't work:

1
- cache(:action => 'home', :action_suffix => 'advertisements', :expires_in => 10.minutes) do

Why? Well, the cache method takes two hashes, and if you leave off the curly braces, that expires_in ends up in the first one, which is used for the name... To roll it right do:

1
- cache({:action => 'home', :action_suffix => 'advertisements'}, :expires_in => 10.minutes) do

Duh, the more you know.

Post to Twitter Post to Digg Post to Facebook Post to Reddit Post to StumbleUpon

Tagged as: , , , 1 Comment
10Feb/100

A Rails rake file for compressing your Javascript with YUI

Here is a quick rake file I threw together to use with my Ruby YUI 2.0 (I-refuse-to-make-a-gem-
edition).

This of course should be used with the Ruby YUI Compressor I posted about the other day.

This script will tar/gzip all of your javascripts just incase something stupid happens. So you have a little safety net.

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
require 'fileutils'
require 'pathname'
 
namespace :js do
  desc <<-INFO
Output Compressed JavaScript to STDOUT; 
  COMPRESS=ALL to compress all Javascript files that DONT contain ".min." 
INFO
 
  task :yui => :environment do
    targz()
    if ENV['COMPRESS'] == 'ALL'
      Dir[Rails.root.to_s / :public / :javascripts / '**/*.js'].each do |javascript|
        next if javascript =~ /\.min\./
 
        compress(javascript)
      end
    else
      if File.exist?(Rails.root + "config/yui.yml")
        javascripts = YAML.load(File.open("config/yui.yml").read)["javascripts"]
 
        if !javascripts.blank?
          javascripts.each {|script| compress(Rails.root + "public/javascripts" + script)}
        else
          raise Exception, "No javascript files in config/yui.yml" 
        end
      else
        raise Exception, "config/yui.yml Not Found; Do rake js:generate to create one or COMPRESS=ALL to run without a config file"
      end
    end
  end
 
  desc "Generate YUI Compressor Config file"
  task :generate => :environment do
    config_path = Rails.root + "config/yui.yml"
    File.open(config_path, "w+") do |f|
      f.puts <<-CONFIG
---
javascripts:
  - "application.js"
  - "jquery.js"
CONFIG
      puts config_path
    end
  end
 
  def targz()
    tgz_file = "#{Time.now.to_i}.javascripts.tgz"
    `cd #{Rails.root + "public"}; tar -zcf #{tgz_file} javascripts/`
    puts "Backed up assets to: #{tgz_file}"
  end
 
  def compress(path)
    puts "Compressing #{path}"
    file_handle = File.open(path)
    compressed_output = YUI.compress_safe file_handle
    file_handle.close
 
    #overwrite the file
    File.open(path, "w+") { |file| file.puts compressed_output }
  end
 
end

Wanna use it?
1. Drop it in your lib/tasks folder or wherever your Rakefile looks

You can compress 'everything' or specific files.

If you want to only compress specific Javascript files do:

1
2
3
rake js:generate 
# Edit your config/yui.yml file
rake js:yui

If you want to compress "everything" (It actually won't compress files that contain ".min.". You can remove the regexp's if you don't like it.)

1
rake js:yui COMPRESS=ALL

Yay, now your raking it in (Pun harhar) w/ your web2.0 yui compressed rails app. Woot woot.

Post to Twitter Post to Digg Post to Facebook Post to Reddit Post to StumbleUpon

Tagged as: , , , No Comments
27Jan/101

An Excel CSV exporter for ActiveRecord

This is a mix of two blog posts on exporting ActiveRecord data to CSV. This explicitly was designed to export stuff so excel wouldn't freak out.

This strips new lines from any string field, set the BOM and converts the encoding to utf16.

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
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
require "fastercsv"
require "iconv"
 
# Excel info from: http://blog.plataformatec.com.br/2009/09/exporting-data-to-csv-and-excel-in-your-rails-app/?utm_source=feedburner&utm_medium=feed&utm_campaign=Feed:+PlataformaBlog+(Plataforma+Blog)
# Original post: http://www.brynary.com/2007/4/28/export-activerecords-to-csv
 
# Usage:
#   Given User is an ActiveRecord model
#   
#   Options for Model.to_csv, Array.to_csv, and ActionController::Base#send_csv
# 
#   :only     - Array, trumps :exclude. Only these attributes will be included instead of all attributes (things listed in :methods will still be sent)
#     :only => [ :id, :name, :created_at ]
#   :methods  - Array, additional methods to evaluate and add to the CSV response. Note: you can send nested method calls
#     :methods => [ :to_s, :complex_method, "my.method.on.a.related.object"]
#   :exclude  - Array, attributes to exclude from CSV result
# 
#  From ActionController
#     send_csv User, :only => [:first_name, :email, :created_at] #This will do all records
#     send_csv User.all(:conditions => ["created_at > ?", some_date]), :only => [:first_name, :email, :created_at]
#
#  From ActiveRecord Model
#   User.to_csv :only => [ :username, :email, :created_at ], :methods => [ :age, "account.id"] # All users additionally get their related Account#id number
#   User.all(:limit => 10).to_csv :exclude => [:password, :birthdate]
#
class ActiveRecord::Base
 
  # Shortcut for CSV of whole table
  def self.to_csv(*args)
    find(:all).to_csv(*args)
  end
 
  # Get the column headers
  def self.csv_columns(options={})
    tmp_columns = if options[:only]
      options[:only] #only trumps exclude
    else
      self.content_columns.map{|curr_col| curr_col.name } - options[:exclude].map{|curr_col| curr_col.to_s }
    end
 
    tmp_columns + options[:methods].map{|curr_col| curr_col.to_s }
  end
 
  # Record to a row level csv array
  def to_csv(options={})
    self.class.csv_columns(options).map { |curr_col|
      curr_col = curr_col.to_s  
 
      #Its a chain of method calls, on intermediary nil, just return nil
      if !curr_col.index(".")
        col_val = self.send(curr_col) 
      else
        col_val = self
        curr_col.split(".").each {|curr_method| col_val = col_val.send(curr_method) unless col_val.nil?}
      end
 
      col_val.gsub!("\n", " ") if col_val.is_a?(String) # Strip newlines, Appease Excel Gods      
      col_val
    }
  end
end
 
 
class Array
 
  # Convert an array of objects into a CSV String.
  def to_csv(options = {})
    column_options    = {
      :only    => options.delete(:only),
      :exclude => options.delete(:exclude) || [],
      :methods => options.delete(:methods) || []
    }
 
    options = {
      :col_sep => "\t" # Appease Excel Gods
    }.merge(options)
 
    if all? { |e| e.respond_to?(:to_csv) }
      header_row = first.class.csv_columns(column_options).to_csv
 
      content_rows = map { |e| 
        # Get all the values of non-excluded rows
        e.to_csv(column_options) 
      }.map{|r| 
        # Call to_csv on the array of row-level values, this will join them into a CSV row
        r.to_csv(column_options) 
      }
 
      ([header_row] + content_rows).join
    else
      FasterCSV.generate_line(self, options)
    end
  end
 
end
 
 
# module for extending ActionController
module ExcelCSVExporter
  BOM = "\377\376" #Byte Order Mark, Appease Excel Gods
 
  def send_csv(kollection, options={})
    filename = options.delete(:filename) || I18n.l(Time.now, :format => :short) + ".csv"
 
    content = kollection.to_csv(options)
 
    # Appease Excel Gods
    content = BOM + Iconv.conv("utf-16le", "utf-8", content)
    send_data content, :filename => filename    
  end
end
 
ActionController::Base.send :include, ExcelCSVExporter

Wanna use it?

1
2
3
4
5
6
7
class MyCoolController < ActionController::Base
  def index
    respond_to {|want|
      want.csv{ send_csv MyCoolModel.all, :exclude => [:secret_column], :methods => [:complex_method, "related.table.method"]}
    }
  end
end

Post to Twitter Post to Digg Post to Facebook Post to Reddit Post to StumbleUpon

Tagged as: , , , 1 Comment