Props to Jeremy Voorhis for this cool and simple Hash maneuver.
class Hash
def rewrite mapping
inject({}) do |rewritten_hash, (original_key, value)|
rewritten_hash[mapping.fetch(original_key, original_key)] = value
rewritten_hash
end
end
end
Example usage:
h = { :human => 'squishy', :robot => 'tinny' }
h.rewrite(:human => :mammal)
# => { :mammal => 'squishy', :robot => 'tinny' }
Interestingly, this is the first time I’ve seen argument decomposition used in a truly clear and helpful way.
In a Rails Migration, specifying a column as binary generates a blob column. Blob columns, unfortunately, are allocated differently than any other MySQL column type; specifically, they aren’t supported in memory engine-backed tables.
Varbinary columns, on the other hand, are supported. In order to provide varbinary columns in a Migration, I wrote up this simple core extension.
# Specify :varbinary => true in column creation
# No support yet for altering tables to varbinary
class ActiveRecord::ConnectionAdapters::MysqlAdapter < AbstractAdapter
def type_to_sql_with_varbinary(type, limit = nil, precision = nil, scale = nil)
return type_to_sql_without_varbinary(type, limit, precision, scale) unless :varbinary == type.to_sym
"varbinary(#{limit})"
end
alias_method_chain :type_to_sql, :varbinary
end
class ActiveRecord::ConnectionAdapters::TableDefinition
def column_with_varbinary name, type, options = {}
return column_without_varbinary name, "varbinary", options if options.delete(:varbinary)
column_without_varbinary name, type, options
end
alias_method_chain :column, :varbinary
end
def foo
return 1
ensure
return 2
end
foo
=> 2
When trying to set up indices on a set of mysql tables, a number of annoying errors can occur that necessitate re-running a migration.
This simple hack lets you specify a :force => true on add_index or remove_index calls.
# Allows you to specify indices to add in a migration that will only be created if they do not
# already exist, or to remove indices only if they already exist
module ActiveRecord
module ConnectionAdapters
class MysqlAdapter
def add_index_with_unless_exists table_name, column_name, options = {}
if options.kind_of?(Hash) && options.delete(:force)
begin
add_index_without_unless_exists table_name, column_name, options
rescue
if /^Mysql::Error: Duplicate key name /.match($!.message)
puts "Failed to create index #{table_name} #{column_name} #{options.inspect}"
else
raise $!
end
end
else
add_index_without_unless_exists table_name, column_name, options
end
end
alias_method_chain :add_index, :unless_exists
def remove_index_with_unless_doesnt_exist table_name, options = {}
if options.kind_of?(Hash) && options.delete(:force)
begin
remove_index_without_unless_doesnt_exist table_name, options
rescue
if /^Mysql::Error: Can't DROP/.match($!.message)
puts "Failed to drop index #{table_name} #{options.inspect}"
else
raise $!
end
end
else
remove_index_without_unless_doesnt_exist table_name, options
end
end
alias_method_chain :remove_index, :unless_doesnt_exist
end
end
end
Thanks to Jan Friedrich who pointed out the turn gem which displays what test is running, and when the test finishes, whether that test passed or failed. It’s an excellent tool that hasn’t received enough visibility.
See this comment for installation instructions.
Test::Unit doesn’t output the specifics of test failure and errors until the entire suite has completed running.
With a large enough (read: slow) suite, a lot of time can be wasted just waiting for tests to finish in order to figure out what’s actually broken.
Well, this is Ruby, so let’s just patch Test::Unit to do what we want. Add this to your test_helper.rb file, or put it somewhere handy. If there’s demand, I may package this up a bit better.
require 'test/unit/testresult'
class Test::Unit::TestResult
alias :add_failure_original :add_failure
def add_failure failure
add_failure_original failure
display_fault_now failure
end
alias :add_error_original :add_error
def add_error error
add_error_original error
display_fault_now error
end
def display_fault_now fault
puts ""
puts fault.long_display
puts ""
end
end
A little-known and very useful feature Rails brings to the table in terms of testing is a pair of callback hooks for Test::Unit::TestCase setup and teardown.
The callback chains were introduced as part of the move toward a custom ActiveSupport::TestCase, but all the functionality of ActiveSupport::TestCase is exposed to Test::Unit::TestCase if you’re running Rails.
Having these hooks exposed allows you to inject setup and teardown behavior without stepping on the toes of any individual test.
class Test::Unit::TestCase
def self.does_not_perform_validations klass
setup do
@preserved_validations = klass.validate.clone
klass.validate.clear
end
teardown do
@preserved_validations.each do |validation|
klass.validate << validation
end
end
end
end
Obviously this example is a little contrived, but perhaps you see how this can be used.
class MarshmallowTest < Test::Unit::TestCase
does_not_perform_validations Marshmallow
…
end
Now with a single macro, we can inject setup and teardown behavior for a model. The same technique can be used to inject teardown hooks from within individual tests, but beware, hooks injected in this way need to remove themselves from the callback chain when executing, or else they’ll be executed for every subsequent test teardown.
module Foo
def shared
'Foo shared'
end
def foobar
shared
end
end
module Moo
def shared
'Moo shared'
end
def moobar
shared
end
end
class Wingnut
include Moo
include Foo
end
w = Wingnut.new
w.foobar # => "Foo shared"
w.moobar # => "Foo shared"
In the old days, we did this:
User.find(:all, :conditions => { :subscribed => true }, :order => 'created_at DESC' )
And then this:
User.find_all_by_subscribed(true, :order => 'created_at DESC' )
How about this?
User.for_subscribed(true).order_by('created_at DESC').find(:all)
Here’s the how!
class User < ActiveRecord::Base
named_scope_for :subscribed
end
Whup-cha!
class ActiveRecord::Base
def self.named_scope_for attribute
named_scope "for_#{attribute}",
lambda { |attribute_value| { :conditions => { attribute => attribute_value } } }
end
named_scope :order_by, Proc.new { |*attributes|
raise ArgumentError, 'You must specify an attribute to order by' if attributes.blank?
{ :order => attributes.join(', ') }
}
end
Check it out it here, or install using git.
git clone git://github.com/duncanbeevers/named_scope_for.git vendor/plugins/named_scope_for
When dealing with ActionScript’s ExternalInterface.addCallback the method to be exposed might not be immediately available. whenAvailable polls until a property shows up on a javascript object, at which point it invokes the provided callback.
function whenAvailable(availableObject, availableProperty, onAvailable) {
if ('object' != typeof(availabilityCheckers)) {
availabilityCheckers = {};
}
var checkAvailabilityFunction = function(){
if('undefined' != typeof(availableObject[availableProperty])){
checker = availabilityCheckers[checkerInterval];
clearInterval(checker.interval);
checker.onAvailable(availableObject, availableProperty, availableObject[availableProperty]);
}
};
var checkerInterval = setInterval(checkAvailabilityFunction, 500);
availabilityCheckers[checkerInterval] = {
interval: checkerInterval,
onAvailable: onAvailable
}
}
Some of my co-workers like to use Notational Velocity to store sensitive information. Though a fine application, I’m partial to my existing tools and wanted something more general-purpose and flexible.
To that end, I set up a simple encrypted disk image and configured it to mount with a password prompt when I log on.
-
Open Disk Utility and select New Image from the toolbar.
-
Configure the Disk Image. I don’t have any big files to encrypt so I opted for the smallest (10MB) image size. Select an option from the Encryption drop-down.
-
Set up a password for your disk image.
-
In order to require password entry to open the disk image you’ll need to set up the permissions in Keychain Access.
-
Under Access Control for the disk image Keychain entry, remove diskimages-helper from the list of programs always allowed to access the entry.
-
In the Accounts Preference Pane, configure the disk image to mount on login.
Now simply select the mounted volume as the destination for files you want encrypted and sleep easy.