methods_like helper for finding ruby methods
Here is a little gem I have in my ~/.irbrc I use it a lot when debugging code. I tend to use call #methods a lot on objects to figure out how they quack.
Sometimes I can remember kinda what a method's name is I want to use, but doing something like the following can generate a huge amount of output especially when working with ActiveRecord
1 | my_object.methods.sort |
So I threw together this little method that adds to ruby's base Object class
1 2 3 4 5 6 7 8 | class Object def methods_like(pat) pattern = pat.is_a?(String) ? /#{pat}/ : pat self.methods.sort.select{|m| m =~ pattern } end end |
Now from IRB, Rails console, or Padrino console you can do things like:
my_collection.methods_like(/^update/) #=> [ array of method names that start with update ] user.methods_like("name") #=> [ :first_name, :last_name, :etc ] Array.new.methods_like "sort" #=> ["sort", "sort!", "sort_by"]
Its a handy little snippet when you can't remember an exact method name, and generally for me works faster than googling for it. Toss it in your ~/.irbrc and tell me what you think.
UPDATE
I added some more functionality for getting additional methods (private, protected, singleton):
class Object # pattern - string or pattern to match # access - :public, :private, :protected, :singleton, :all def methods_like(pattern, access = :public, details = false) master_method_list = case access when :public then self.methods when :private then self.private_methods when :protected then self.protected_methods when :singleton then self.singleton_methods when :all ( self.methods + self.private_methods + self.protected_methods + self.singleton_methods ).uniq end matched_method_list = master_method_list.sort.select{ |m| m =~ ( pattern.is_a?(String) ? /#{pattern}/ : pattern ) } if !details matched_method_list else details_list = {} matched_method_list.each do |current_method| details_list[current_method] = method_details(current_method) end details_list end end # Returns details about method def method_details(current_method) current_method = current_method.to_s access_level = if self.methods.include?(current_method) :public elsif self.private_methods.include?(current_method) :private elsif self.protected_methods.include?(current_method) :protected elsif self.singleton_methods.include?(current_method) :singleton end { :owner => self.method(current_method).owner, :arity => self.method(current_method).arity, :receiver => self.method(current_method).receiver, :access => access_level } end end
Now you can do:
variable.methods_like pattern, :private #=> Array of private methods with pattern variable.methods_like pattern, :all, true #=> Hash of methods with additional details
If you pass 'true' for details you will get a hash that looks like:
Array.new.methods_like "sort", :all, true #=> { "sort_by"=>{:owner=>Enumerable, :access=>:public, :receiver=>[], :arity=>0}, "sort!" =>{:owner=>Array, :access=>:public, :receiver=>[], :arity=>0}, "sort" =>{:owner=>Array, :access=>:public, :receiver=>[], :arity=>0} }
Access options available are :public, :private, :singleton, :protected, :all
Keeping a Software Development Journal or Diary
I'm the kind of person that keeps tons of notes on my thoughts as I am developing on a new project. I generally abuse the README file in my project. It was a mixture of my notes and other information for the project organized by topic - where applicable.
Recently I started keeping a JOURNAL file in my projects that was a day-by-day dump of what I was thinking and why I chose certain choices. It is a really nice way of organizing your thoughts and providing a way to look back through "what you were thinking." It also provides other developers working on the project a way to look at how you came to a certain conclusion.
I was originally doing this in markdown, but recently went searching for TextMate plugin. I found a really great one called JournalTasks.
I've been making two entries a day on my project. One I keep 'open' all day dumping my thoughts on implementation and any interesting choices as well as good references I find while doing research. I have a second one I make when I'm wrapping up as a checklist of what was accomplished (pretty much a git commit log and some additional notes) and what I need to work on next. The plugin also outputs using markdown, supports tagging/searching your posts and simple tasks support.
If you aren't keeping a journal of your thoughts during development, I'd suggest it. A well organized record of your brain; its a good thing.
Thanks to Alan Schussman for a great bundle (and saving me the time of writing one).
Testing 404/500, Error Pages with Webrat visit method
This is a super simple snippet, and maybe there is something built into webrat, but I didn't find it. I needed to test for 404 pages in some of my tests (I was writing tests that made sure a page didnt show for disabled content). Purposely getting 404's can be annoying though since webrat opens up the 404 page in your default browser. There is a variable available in Webrat's configuration that you can use to temporarily disable this functionality. I wrapped it in the following method:
1 2 3 4 5 | def visit_but_dont_open(url) Webrat.configuration.instance_variable_set("@open_error_files", false) visit url Webrat.configuration.instance_variable_set("@open_error_files", true) end |
Throw that in your test_helper and you can start doing:
1 2 | visit_but_dont_open "Http://example.com/page/that/doesnt/exist" assert !response.ok? |
Yay, 404.
Ruby 1.9 Date#strftime adds a space on %b
So, I had a spec failing in my integration suite and I couldn't figure out what the hell it was - EVERYTHING LOOKED LEGIT. I even logged into the site, and visually verified it. Whats the dilly?
I upgraded to ruby 1.9 from ruby 1.8 and apparently, when doing strftime on date, you get a free whitespace character with "%b"
1 2 3 | # Ruby 1.8 irb(main):006:0> Date.today.strftime("%b %e, %Y") => "May 7, 2010" |
1 2 | irb(main):007:0> Date.today.strftime("%b %e, %Y") => "May 7, 2010" # TWO SPACES |
Super dumb - wasted a good 10 minutes of my day because I could see the diff between:
1 2 | "May 7, 2010" "May 7, 2010" |
In the middle of a web page full of other content.
Why the freebee space character on the %b? Dunno. I just smashed my stftime statement together and my specs are rolling.
1 | Date.today.strftime("%b%e, %Y") |
Uh, yeah - this is my bug report.
Padrino, Compass, and Sass – Working happily via Ian Serlin
My cohort, Ian Serlin, discovered this. In a project we are working on we could get Compass to play well with PadrinoRb. It seems like the #sass method doesn't care about the options being passed to it, and we kept getting stack traces rendered into our CSS files. The stack trace was ruby looking for - and failing to find compass/reset.css.
This is the code that DOESN'T work (padrino 0.9.10, compass 0.8.17, sinatra 1.0).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | configure do register SassInitializer #The Rack Sass reloader... Compass.configuration do |config| config.project_path = File.dirname(__FILE__) config.sass_dir = "stylesheets" config.project_type = :stand_alone config.http_path = "/" config.css_dir = "stylesheets" config.images_dir = "images" config.output_style = :compressed end end get '/stylesheets/:file.css' do content_type 'text/css', :charset => 'utf-8' # This is the doc on how to give Sass some more load paths to find the compass files. # it doesnt work :P and for that matter, neither does: # sass :file, Compass.sass_engine_options # or the set :sass, whatever_hash_here sass :file, :sass => Compass.sass_engine_options end |
Our solution was to force Sass options and Compass options to merge...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | configure do register SassInitializer Compass.configuration do |config| config.project_path = File.dirname(__FILE__) config.sass_dir = "stylesheets" config.project_type = :stand_alone config.http_path = "/" config.css_dir = "stylesheets" config.images_dir = "images" config.output_style = :compressed end Sass::Plugin.options.merge!(Compass.sass_engine_options) end get '/stylesheets/:file.css' do content_type 'text/css', :charset => 'utf-8' sass :file end |
Yay, magic spells - it works. You can check out more ian magic spells at ianserlin.com.
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:
- #original_filename
- #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 |
A ruby wrapper for Google AjaxLibs (jquery, jquery ui, mootools, prototype, swfobject, etc) [Lazy GoogleJsApi Wrapper]
I like using the Google AjaxLibs API. Its cool to not host files when I don't have to, especially ones I know a user has already probably cached from Google anyway (even though Google Page speed busts my balls about too many DNS lookups
).
I do on the other hand hate watching in the bottom of my browser "Waiting for google.com" when using the standard Javascript API for loading the Libraries like so:
1 2 3 4 | <script type="text/javascript" src="http://www.google.com/jsapi?key=INSERT-YOUR-KEY"></script>
<script type="text/javascript">
google.load("jquery", "1.4.2");
</script> |
And you don't have to wait, you can totally do something like:
1 | <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script> |
Which is why I wrote this little ruby wrapper. Its nothing special. It just encapsulates all the URLs for the libraries and their versions to allow you to not remember the URL and not have to go looking for the documentation when you start a new app or just need an additional library.
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 | # Docs: http://code.google.com/apis/ajaxlibs/documentation/index.html # class GoogleJsApi BaseURL = "http://ajax.googleapis.com/ajax/libs".freeze Libraries = { "jquery" => { :versions => %w(1.2.3 1.2.6 1.3.0 1.3.1 1.3.2 1.4.0 1.4.1 1.4.2), :compressed_url => "/jquery/%s/jquery.min.js", :uncompressed_url => "/jquery/%s/jquery.js" }, "jqueryui" => { :versions => %w(1.5.2 1.5.3 1.6 1.7.0 1.7.1 1.7.2), :compressed_url => "/jqueryui/%s/jquery-ui.min.js", :uncompressed_url => "/jqueryui/%s/jquery-ui.js" }, "prototype" => { :versions => %w(1.6.0.2 1.6.0.3 1.6.1.0), :compressed_url => "", :uncompressed_url => "/prototype/%s/prototype.js" }, "scriptaculous" => { :versions => %w(1.8.1 1.8.2 1.8.3), :compressed_url => "", :uncompressed_url => "/scriptaculous/%s/scriptaculous.js" }, "mootools" => { :versions => %w(1.1.1 1.1.2 1.2.1 1.2.2 1.2.3 1.2.4), :compressed_url => "/mootools/%s/mootools-yui-compressed.js", :uncompressed_url => "/mootools/%s/mootools.js" }, "dojo" => { :versions => %w(1.1.1 1.2.0 1.2.3 1.3.0 1.3.1 1.3.2 1.4.0 1.4.1), :compressed_url => "/dojo/%s/dojo/dojo.xd.js", :uncompressed_url => "/dojo/%s/dojo/dojo.xd.js.uncompressed.js" }, "swfobject" => { :versions => %w(2.1 2.2), :compressed_url => "/swfobject/%s/swfobject.js", :uncompressed_url => "/swfobject/%s/swfobject_src.js" }, "yui" => { :versions => %w(2.6.0 2.7.0 2.8.0r4), :compressed_url => "/yui/%s/build/yuiloader/yuiloader-min.js", :uncompressed_url => "/yui/%s/build/yuiloader/yuiloader.js" }, "ext-core" => { :versions => %w(3.0.0 3.1.0), :compressed_url => "/ext-core/%s/ext-core.js", :uncompressed_url => "/ext-core/%s/ext-core-debug.js" }, "chrome-frame" => { :versions => %w(1.0.0 1.0.1 1.0.2), :compressed_url => "/chrome-frame/%s/CFInstall.min.js", :uncompressed_url => "/chrome-frame/%s/CFInstall.js" } }.freeze class << self def include(name, version=nil, compressed=true, validate_version=true) name = name.to_s if lib = GoogleJsApi::Libraries[name] version ||= lib[:versions].last if validate_version && lib[:versions].include?(version) GoogleJsApi.url_for(name, version, compressed) elsif !validate_version GoogleJsApi.url_for(name, version, compressed) else raise Exception, "Invalid version (#{version}) for #{name}" end else raise Exception, "Unknown Google Javascript Library" end end def version_info tmp_version = {} GoogleJsApi::Libraries.each do |k,v| tmp_version[ k ] = v[:versions] end tmp_version end protected def url_for(name, version, compressed) GoogleJsApi::BaseURL + GoogleJsApi::Libraries[ name ][ compressed ? :compressed_url : :uncompressed_url ] % version end end end |
Here are some tests for it if you are interested in how/if it works:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | require 'pp' pp GoogleJsApi.version_info # Include the newest version of a library puts GoogleJsApi.include('jquery') # Include a specific version of a library puts GoogleJsApi.include "chrome-frame", "1.0.0" # Include a specific version of a library uncompressed puts GoogleJsApi.include "chrome-frame", "1.0.0", false # Include a specific version of a library w/o validating the version; useful if this goes out of date :P puts GoogleJsApi.include "jquery", "NOT_A_VERSION", false, false begin puts GoogleJsApi.include :jqueryui, "1.0" rescue Exception => e puts "This should blow up..." puts e.message end |
If you want to use it in your Rails or Merb app you can do:
1 2 3 4 5 | # Rails require 'google_js_api' #or whatever you named the file when you dropped it in lib # In your application layout or whatever javascript_include_tag GoogleJsApi.include(:jquery, "1.4.0") |
1 2 3 4 5 | # Merb require 'google_js_api' #or whatever you named the file when you dropped it in lib #in your application layout or where ever require_js GoogleJsApi.include(:swfobject) |
Its simple, its lazy. I like it.
Lower ACL Permissions on Amazon S3 items with ruby
I recently had to change a bunch of permissions on some items on S3. Unfortunately the items were mixed in with items that I didn't want to change the permissions on and the file names are all kinda jumbled, so I couldn't pinpoint what I needed to change by eyeballing the file names.
So I wrote a little ruby method to go in and change something given a key and bucket name.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | require 'aws/s3' AWS::S3::Base.establish_connection!( :access_key_id => "YOUR_ACCESS_KEY", :secret_access_key => "YOUR_SECRET_KEY" ) include AWS::S3 def s3_set_public_read(key, bucket) puts "Processing: #{key}" policy = S3Object.acl(key, bucket) policy.grants << ACL::Grant.grant(:public_read) policy.grants << ACL::Grant.grant(:public_read_acp) S3Object.acl(key, bucket, policy) end |
Now I can just run a loop of ActiveRecord objects around that method and convert them all to public read.
If you want a GUI tool for managing S3 stuff, I totally recommend S3Hub. Its a really sexy tool for managing S3.
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.
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.