Rake tab completion with caching and namespace support
UPDATE: It now invalidates the cache if you touch lib/tasks/*.rake, for those using it with rails (like me)
There’s a few articles on the net regarding rake tab completion, I had to combine a few of them to get what I wanted:
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 |
#!/usr/bin/env ruby # Complete rake tasks script for bash # Save it somewhere and then add # complete -C path/to/script -o default rake # to your ~/.bashrc # Xavier Shay (http://rhnh.net), combining work from # Francis Hwang ( http://fhwang.net/ ) - http://fhwang.net/rb/rake-complete.rb # Nicholas Seckar <nseckar@gmail.com> - http://www.webtypes.com/2006/03/31/rake-completion-script-that-handles-namespaces # Saimon Moore <saimon@webtypes.com> require 'fileutils' RAKEFILES = ['rakefile', 'Rakefile', 'rakefile.rb', 'Rakefile.rb'] exit 0 unless RAKEFILES.any? { |rf| File.file?(File.join(Dir.pwd, rf)) } exit 0 unless /^rake\b/ =~ ENV["COMP_LINE"] after_match = $' task_match = (after_match.empty? || after_match =~ /\s$/) ? nil : after_match.split.last cache_dir = File.join( ENV['HOME'], '.rake', 'tc_cache' ) FileUtils.mkdir_p cache_dir rakefile = RAKEFILES.detect { |rf| File.file?(File.join(Dir.pwd, rf)) } rakefile_path = File.join( Dir.pwd, rakefile ) cache_file = File.join( cache_dir, rakefile_path.gsub( %r{/}, '_' ) ) if File.exist?( cache_file ) && File.mtime( cache_file ) >= (Dir['lib/tasks/*.rake'] << rakefile).collect {|x| File.mtime(x) }.max task_lines = File.read( cache_file ) else task_lines = `rake --silent --tasks` File.open( cache_file, 'w' ) do |f| f << task_lines; end end tasks = task_lines.split("\n")[1..-1].collect {|line| line.split[1]} tasks = tasks.select {|t| /^#{Regexp.escape task_match}/ =~ t} if task_match # handle namespaces if task_match =~ /^([-\w:]+:)/ upto_last_colon = $1 after_match = $' tasks = tasks.collect { |t| (t =~ /^#{Regexp.escape upto_last_colon}([-\w:]+)$/) ? "#{$1}" : t } end puts tasks exit 0 |
Finding related content with Sphinx
Previous efforts to find related posts with the classifier gem yielded no fruit, so I tried another approach using sphinx. Turned out to be a winner.
The basic theory is to index all posts by tag, then to find related posts just use the current post’s tags as a search string. Remember to exclude the current post from the search results. For this blog, I use tags for the main categories, which were corrupting the results – most everything is tagged ‘Ruby’ so it doesn’t add any value in determining likeness. So rather than indexing all tags I excluded some of the main ones.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
class Post < ActiveRecord::Base has_many :searchable_tags, :through => :taggings, :source => :tag, :conditions => "tags.name NOT IN ('Ruby', 'Code', 'Life')" def related_posts(number = 3) Post.search(:limit => number + 1, :conditions => { :tag_list => tag_list.join("|") }).reject {|x| x == self }.first(number) end define_index do indexes searchable_tags(:name), :as => :tag_list # If you want to use this for normal search as well you'll have to # add in title/body here as well end end |
For a more complete example, see the relevant RHNH commits: cdc0bf and d4d844
Showing links to related content is a good way to stop the bottom of your page from being a ‘dead end’. In the event that no related posts are found, I’m linking to the archives instead.
Hash trumps case
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
# Two equivalent functions def rgb(color) case color when :red then 'ff0000' when :green then '00ff00' when :blue then '0000ff' else '000000' # Default to black end end def rgb2(color) { :red => 'ff0000', :green => '00ff00', :blue => '0000ff' }[color] || '000000' end |
Even though these functions are equivalent, the second carries more semantic weight – it maps a symbol directly to a color. The case sample makes no such guarantees since you can execute any arbitrary code in the then block. In addition, a hash is easier to work with – you can easily iterate over the keys, extract to another method if you need reuse, or query it for other properties (for example, 3 colors are available). It is also easier to read – both aesthetically and because it contains fewer tokens. In almost all circumstances I will prefer a hash over a case statement.
Relationships in data are easier to comprehend and manipulate than relationships in code.
Contextual Composition With Delegation
I’ve had some models getting rather large recently. This makes them hard to comprehend and makes the source difficult to browse. A lot of the time, a big chunk of functionality is fairly context specific – it is only relevant to one particular part of my application (reporting, data integration, etc…). Thoughtbot presented one way to do this recently by adding methods to the model that return another model with the extra goodness.
That’s not bad, but it still pollutes the class with methods that most users won’t care about. We can just decorate the class with extra methods at the time (context) that we need them. My first go at doing this used the extend method:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
class PurchaseOrder attr_reader :id end module Reports::PurchaseOrderMethods def description "A Purchase Order" end end class ReportMakerWithExtend def self.report_for(po) po.extend(Reports::PurchaseOrderMethods) "#{po.id}: #{po.description}" end end |
This has a few edge case problems though.
- It can potentially override methods in our base class. Imagine if PurchaseOrder#description was defined as private, our module would override this defenition resulting in probably breakage.
- It is inelegant to test –
extendwill override any existing stubs, so you need to stub it out. This is unintuitive and may have unintended consequences, for instance if the class is also usingextendin a manner that doesn’t interfere with your stubs.
1 2 3 4 5 6 7 8 9 10 11 |
# Testing extended PurchaseOrder is inelegant describe 'ReportMakerWithExtend#report_for' do it 'returns a line containing both ID and description' do po = stub( :id => 1 :description => "hello", :extend => nil # :( ) ReportMaker.report_for(po).should == "1: hello" end end |
Ruby provides another method to achieve what we want in the form of SimpleDelegator. Basically, it passes on any methods not defined on itself to the object specified in the constructor. This way we can wrap another object without fear of interferring with its internals nor our stubs.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
require 'delegate' class Reports::PurchaseOrder < SimpleDelegator def description "A Purchase Order" end end class ReportMaker def self.report_for(po) po = Reports::PurchaseOrder.new(po) "#{po.id}: #{po.description}" end end |
Much nicer. Of course, we would have specs for Reports::PurchaseOrder in addition to PurchaseOrder – this split allows us to keep our tests focussed and easy to read. Using delegation to split up your models allows you to separate code into areas where it is most relevant – helping keep both your models and your tests easy to read and maintain.
Testing flash.now with RSpec
flash.now has always been a pain to test. The the traditional rails approach is to use assert_select and find it in your views. This clearly doesn’t work if you want to test your controller in isolation.
Other folks have found work arounds to the problem, including mocking out the flash or monkey patching it.
These solutions feel a bit like using a sledgehammer to me. If you’re going to monkey patch/mock something, you want it to be as discreet as possible so to minimize the chance of the implementation changing underneath you and also to reduce the affect on other areas of your application. Also, why duplicate perfectly good code that is provided elsewhere?
The real problem with testing flash.now is that it gets cleaned up (via #sweep) at the end of the action before you get to test anything. So let’s solve that problem and that problem only: disable sweeping of flash.now:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
# spec/spec_helper.rb module DisableFlashSweeping def sweep end end # A spec describe BogusController, "handling GET to #index" do it "sets flash.now[:message]" do @controller.instance_eval { flash.extend(DisableFlashSweeping) } get :index flash.now[:message].should_not be_nil end end |
instance_eval is used to access the flash, since it’s a protected method, and we extend with the minimum possible code to do what we want – blanking out the sweep method. This should not cause problems because sweeping is only relevant across multiple requests, which we shouldn’t be doing in our controller specs.
Classifier gem rubbish for recommending posts
Chatting with Tim today he suggested maybe using Classifier::LSI would be a cool way to offer ‘related posts’ suggestions for a blog.
Not really knowing anything about it, I whipped up a prototype rake task. It creates the index then marshals it to disk because it takes ages to create and it’s not much fun to play with when you have to wait minutes each time. It then presents 3 related suggestions for each post.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
require 'classifier' namespace :lsi do task :test => :environment do if File.exists?("lsidata.dump") lsi = File.open("lsidata.dump") {|f| Marshal.load(f) } else lsi = Classifier::LSI.new Post.find(:all, :order => 'published_at DESC').each do |post| text = post.body categories = post.tags.collect(&:name) puts "Indexing " + post.title lsi.add_item(text, *categories) end File.open("lsidata.dump", "w") {|f| Marshal.dump(lsi, f) } end Post.find(:all).each do |post| puts post.title puts lsi.find_related(post.body, 3).collect {|i| Post.find_by_body(i).title }.inspect end end end |
Here’s the data for my last 5 articles. I don’t know what I was expecting, but this just doesn’t seem very helpful. I don’t have a very rich set of tags on my posts, so that probably has something to do with it. Was kind of hoping it would just look at text and all just work * waves hands *.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
Seagate 500Gb FreeAgent Pro external drive - first impressions - Building Firefox Extensions - The Colemak Diaries - Counting ActiveRecord associations: count, size or length? Coconut Oats - The Colemak Diaries - Summertime Tagliarini - Mary Iron Chef - Chocolate Jaffa Boxes Mary Iron Chef - Chocolate Jaffa Boxes - The Colemak Diaries - Building Firefox Extensions - Summertime Tagliarini Paypal IPN fails date standards - Building Firefox Extensions - Straight Sailing with Magellan - The Colemak Diaries I'm number 8! - Extending Rails - Practical Hpricot: SVG - Day of days |
Next step is to try tagging my stuff better and seeing if that helps out.
Getting classifier working
Quick side note – pure ruby classifier doesn’t work out of the box with rails because it also redefines Array#sum. If you install the GSL lib and the ruby bindings (see classifier docs) you’ll still need this one line patch to classifier to get it to work:
1 2 3 4 5 6 7 8 9 10 11 12 |
Index: lib/classifier/lsi.rb
===================================================================
--- lib/classifier/lsi.rb (revision 31)
+++ lib/classifier/lsi.rb (working copy)
@@ -25,6 +25,8 @@
# please consult Wikipedia[http://en.wikipedia.org/wiki/Latent_Semantic_Indexing].
class LSI
+ include GSL if $GSL
+
attr_reader :word_list
attr_accessor :auto_rebuild
|
UPDATE: I’ve forked classifier on github, so you can just grab that version if you like.
I'm number 8!
I had no idea Working With Rails ran a monthly hackfest. Basically, you contribute to rails, you get points, at the end of the month you can win stuff. To my surprise, I came in at #8 last month and got a free copy of “Make” magazine from O’Reilly.
Sweet. Thank you doc patches.
Obligatory thumbs-up-with-swag photo:
Paypal IPN fails date standards
Paypal Instant Payment Notification lets you know when you have received a paypal payment. Presumably, you then mark an order as paid or something. Do not use the current time as the paid_at date – despite the ‘instant’ in the title it can be many days later. You should use the payment_date provided by paypal. Your accountant will thank you.
payment_date is:
Time/Date stamp generated by PayPal system [format: “18:30:30 Jan 1, 2000 PST”]
Seen that date format before? No? Didn’t think so. That’s no RFC I’ve seen before. The popular Paypal gem uses Time.parse, but this is incorrect (as of 2.0.0). Observe:
1 2 3 4 |
>> Time.parse("18:30:30 Mar 28, 2008 PST") => Fri Mar 28 18:30:30 1100 2008 # Good >> Time.parse("18:30:30 Feb 28, 2008 PST") => Fri Mar 28 18:30:30 1100 2008 # FAIL |
Also, Time only has a range of about a week, so that could screw you over come any major system failures (either you or paypal). Also note the payment_date is in PST, which unless you’re on the right side of the US is fairly useless. I recommend the following:
1 2 |
>> DateTime.strptime("18:30:30 Jan 1, 2000 PST", "%H:%M:%S %b %e, %Y %Z").new_offset(0) => Sun, 02 Jan 2000 02:30:30 0000 |
The un-intuitive new_offset converts to UTC. Patch submitted. I hate you, Paypal.
AtomFeedHelper produces invalid feeds
Summary: atom_feed is broken until changeset 8529
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
# http://api.rubyonrails.org/classes/ActionView/Helpers/AtomFeedHelper.html#M000931 atom_feed do |feed| feed.title("My great blog!") feed.updated((@posts.first.created_at)) for post in @posts feed.entry(post) do |entry| entry.title(post.title) entry.content(post.body, :type => 'html') entry.author do |author| author.name("DHH") end end end end |
Produces the following feed (rails 2.0.2)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
<?xml version="1.0" encoding="UTF-8"?> <feed xml:lang="en-US" xmlns="http://www.w3.org/2005/Atom"> <id>tag:localhost:posts</id> <link type="text/html" rel="alternate" href="http://localhost:3000"/> <title>My great blog!</title> <updated>2007-12-23T04:23:07+11:00</updated> <entry> <id>tag:localhost:3000:Post1</id> <published>2007-12-23T04:23:07+11:00</published> <updated>2007-12-30T15:29:55+11:00</updated> <link type="text/html" rel="alternate" href="http://localhost:3000/posts/1"/> <title>First post</title> <content type="html">Check out the first post</content> <author> <name>DHH</name> </author> </entry> </feed> |
Let’s run that through the feed validator
1 2 3 |
line 3, column 25: id is not a valid TAG line 2, column 0: Missing atom:link with rel="self" line 8, column 32: id is not a valid TAG |
Oh dear. Not a happy result. Let’s fix it.
Problem the first is the feed ID tag. It doesn’t include a date, as per the Tag URI specification. This is a little bit tricky – you can’t just add Time.now.year as a default because that will change every year, and we need IDs to stay the same. We will provide an option to the user to specify the schema date, and produce a warning if they do not (as much as I’d like to just break it, the pragmatic side of me keeps backwards compatibility in).
The entry tag has the same problem, but you’ll also note it concatenates the class and the ID with no separator to create the ID. While it’s an edge case, this will break if you have a class name ending in a number, so we need to add in a separator. I vote for a slash. Also, the port in the tag URI is inconsistent with the feed URI (no port), so remove it.
For further reading, I recommend How to make a good ID in Atom.
The missing self link is just your garden variety bug – the documentation says it should be provided by default, but the code does not.
I went ahead and fixed these problems. Changeset 8529. The example above, when you change the call to atom_feed(:schema_date => 2008), looks like this.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
<?xml version="1.0" encoding="UTF-8"?> <feed xml:lang="en-US" xmlns="http://www.w3.org/2005/Atom"> <id>tag:localhost:/posts</id> <link type="text/html" rel="alternate" href="http://localhost:3000"/> <link type="application/atom+xml" rel="self" href="http://localhost:3000/posts.atom"/> <title>My great blog!</title> <updated>2007-12-23T04:23:07+11:00</updated> <entry> <id>tag:localhost:Post/1</id> <published>2007-12-23T04:23:07+11:00</published> <updated>2007-12-30T15:29:55+11:00</updated> <link type="text/html" rel="alternate" href="http://localhost:3000/posts/1"/> <title>First post</title> <content type="html">HOORAY. About ruby.</content> <author> <name>DHH</name> </author> </entry> </feed> |
mmm, semantic goodness
Lesstile - A yuletide present
Textile is great for formatting articles. But comments aren’t articles, and I have always felt that textile was overkill. Do you really need nested headings and subscript in comments? No.
Also! And more importantly, textile doesn’t output valid XHTML. Consider the following textile code:
1 2 3 4 5 |
<b> Hello This is broken </b> |
Converts to:
1 2 3 |
<p><b> Hello</p> <p>This is broken</b></p> |
That sucks if your blog happens to be XHTML strict, because then your site is broken :( So I made an alternative. I offer it as a present to you: Lesstile
Try it out, it’s pretty neat:
1 |
gem install lesstile |
1 2 3 4 5 6 7 8 9 |
require 'lesstile' Lesstile.format_as_xhtml <<-EOS Wow this is ace! --- Ruby def some_code "yay code" end |
EOS
It supports code blocks, and that’s it. You can easily pass it through CodeRay to get syntax highlighting if you want – see the docs. In the future it may also support hyperlinking. That’s all I suppose commenters on this blog need, maybe you will tell me otherwise. Try it out on this post.
As a special extra treat, I added live preview to this blog, so you can see what your comment is going to look like as you write. It’s just like the future!
Please comment with code to say hi.
Test setup broken in Rails 2.0.2
Some changes went into rails 2.0.2 that mean the setup method in test subclasses won’t get called. Here’s how it went down:
You can see some code illustrating the problem in 8445. This affects two plugins that we’re using – helper_test and activemessaging.
For the helper test, the work around is to rename your helper test setup methods to setup_with_fixtures.
1 2 3 |
def setup_with_fixtures super end |
For activemessaging, add the following line to the setup of your functionals that are failing (from the mailing list):
1 |
ActiveMessaging.reload_activemessaging
|
Understanding the Y Combinator
Many people have written about this, it still took me a long while to figure it out. It’s a bit of a mindfuck. So here is me rehashing what other people have said in a way that makes sense to me.
The Problem
I’ll start with the same example of hash autovivication (that’s what perl calls it) used by Charles Duan in his article. We want the following code to work:
1 2 3 |
hash = Hash.new {|h, k| h[k] = default } # We need to implement default later, read on! hash[1][2][3][4][5] = true hash # => {1=>{2=>{3=>{4=>{5=>true}}}}} |
To do this, we need to specify an appropriate default value for the hash. If we set the default to {}, we only get one level of autovivication.
1 2 3 |
hash = Hash.new {|h, k| h[k] = {} } hash[1] # => {} hash[1][2] # => nil |
Clearly we need a recursive function to support infinite depth, which we can do with a normal ruby method.
1 2 3 4 5 6 |
def make_hash Hash.new {|h, k| h[k] = make_hash } end hash = make_hash hash[1][2][3][4][5] # => {} |
The problem here is we’ve introduced a new method into the namespace (make_hash), which isn’t really necessary. The Y Combinator allows us to achieve the same result, without a named method or variable.
The Solution
We can avoid the need for a named method by wrapping the Hash creation code in an anonymous lambda that passes in the callback as an argument.
1 |
lambda {|callback| Hash.new {|h, k| h[k] = callback.call }}.call(some_callback)
|
We just need a way to pass in a callback function that is the same as the initial function. If you try to copy and paste in the hash maker code, you’ll find it doesn’t quite work because we then need a way to get a callback for that callback.
1 2 3 4 5 6 7 |
lambda {|callback|
Hash.new {|h, k| h[k] = callback.call }
}.call(
lambda {
Hash.new {|h, k| h[k] = callback.call }
}
}) # fails because the second callback isn't defined
|
But we’re getting closer. What if we pass in our initial callback function as a parameter to itself? Then it will know how to call itself over and over again. This is pretty tricky – the first example illustrates the concept using a named method for clarity, the second example is what we actually want.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
# With named method def make_hash(x) Hash.new {|h,k| h[k] = x.call(x)} end hash = make_hash(method(:make_hash)) # With lambdas hash = lambda {|callback| Hash.new {|h, k| h[k] = callback.call(callback) } }.call( lambda {|callback| Hash.new {|h, k| h[k] = callback.call(callback) } }) hash[1][2][3][4][5] # => {}, hooray! |
And that’s really the guts of it. If you understand that you’ve pretty much got it. From here on in it’s just extra credit.
Making it DRY
The previous code repeats itself somewhat – you copy and paste the hash maker function into two spots. Basically, the code is hash = x.call(x). So let’s use another lambda to express it as such.
1 2 3 4 |
lambda {|x| x.call(x) }.call(
lambda {|callback|
Hash.new {|h, k| h[k] = callback.call(callback) }
})
|
Making it work for callbacks with an arbitrary number of parameters
By passing in the callback to itself, we’re restricting ourselves to a callback with no parameters. You’ll notice we’re not able to pass in any parameters to the hash maker above. As you may have guessed, we add another level of abstraction with a lambda that passes in a callback_maker function.
1 2 3 4 5 6 |
hash = lambda {|x| x.call(x) }.call(lambda {|callback_maker|
lambda {|*args|
callback = callback_maker.call(callback_maker)
Hash.new {|h, k| h[k] = callback.call(*args) }
}
}).call("an argument!")
|
So yes, that example is kind of useless because we don’t use the arguments. Let’s try something a bit meatier, say a factorial function.
1 2 3 4 5 6 7 |
lambda {|x| x.call(x) }.call(lambda {|callback_maker|
lambda {|*args|
callback = callback_maker.call(callback_maker)
v = args.first
return v == 1 ? 1 : v * callback.call(v - 1)
}
}).call(5) # => 120
|
Making it generic and pretty
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
def y_combinator(&generator) lambda {|x| x.call(x) }.call(lambda {|callback_maker| lambda {|*args| callback = callback_maker.call(callback_maker) generator.call(callback).call(*args) } }) end y_combinator {|callback| lambda {|v| return v == 1 ? 1 : v * callback.call(v - 1) } }.call(5) # => 120 end |
And let’s make it a bit less ugly by doing what Tom Mortel did and using [] instead of call (they’re equivalent), and moving the callback_maker inline.
1 2 3 4 5 |
def y_combinator(&f) lambda {|x| x[x] } [ lambda {|maker| lambda {|*args| f[maker[maker]][*args] }} ] end |
Thus ends my exploration of the Y Combinator. Practically useless in any language you’d be using today, but hey, don’t you feel smarter?
UPDATE: Added dmh’s suggestion from the comments.
Making cerberus more fun

And throughout the lands of the Greek empire, he was known and feared as Cerberus, the original three-headed party dog from hell
Here is patch to the cerberus campfire publisher that enables it to prepend a funny image to its messages. Submitted to core, guess it depends on how much of a sense of humour the author has.
Someone let GIS know it’s about to be thrashed by queries for train wrecks and hi fives.
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 |
Index: lib/cerberus/config.example.yml
===================================================================
--- lib/cerberus/config.example.yml (revision 167)
+++ lib/cerberus/config.example.yml (working copy)
@@ -17,6 +17,11 @@
# channel: cerberus
# campfire:
# url: http://someemail:password@cerberustool.campfirenow.com/room/51660
+# preamble:
+# # Posts content before the main message based on the build state. Perfect for amusing images.
+# # Valid states are: setup, broken, failed, revival, successful
+# broken: http://mydomain.com/broken.jpg
+# revival: http://mydomain.com/fixed.jpg
# rss:
# file: /usr/www/rss.xml
#builder:
@@ -26,4 +31,4 @@
#hook:
# rcov:
# on_event: successful, setup #by default - run hook for any state
-# action: 'export CERBERUS_HOME=/home/anatol && sudo chown www-data -R /home/anatol/cerberus && rcov' #Add here any hook you want
\ No newline at end of file
+# action: 'export CERBERUS_HOME=/home/anatol && sudo chown www-data -R /home/anatol/cerberus && rcov' #Add here any hook you want
Index: lib/cerberus/publisher/campfire.rb
===================================================================
--- lib/cerberus/publisher/campfire.rb (revision 167)
+++ lib/cerberus/publisher/campfire.rb (working copy)
@@ -3,8 +3,10 @@
class Cerberus::Publisher::Campfire < Cerberus::Publisher::Base
def self.publish(state, manager, options)
url = options[:publisher, :campfire, :url]
+ preamble = options[:publisher, :campfire, :preamble, state.current_state]
subject,body = Cerberus::Publisher::Base.formatted_message(state, manager, options)
+ Marshmallow.say(url, preamble) unless preamble.nil?
Marshmallow.say(url, subject)
Marshmallow.paste(url, body)
end
|
Props to grant for the inspiration and finding of the title photo
Formatting ruby hashes in VIM
I’ve been meaning to write this script for a while. If you’re anal about your whitespace (like I), you’ll often pretty up your ruby hashes to make them easy to read by adding a bit of whitespace to the keys before the =>. I wrote a ruby script to do this automatically!
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 |
#!/usr/bin/env ruby # format_hash.rb # # Formats ruby hashes # a => 1 # ab => 2 # abc => 3 # # becomes # a => 1 # ab => 2 # abc => 3 # # http://rhnh.net lines = [] while line = gets lines << line end indent = lines.first.index(/[^\s]/) # Massage into an array of [key, value] lines.collect! {|line| line.split('=>').collect {|line| line.gsub(/^\s*/, '').gsub(/\s*$/, '') } } max_key_length = lines.collect {|line| line[0].length}.max # Pad each key with whitespace to match length of longest key lines.collect! {|line| line[0] = "%#{indent}s%-#{max_key_length}s" % ['', line[0]] line.join(' => ') } print lines.join("\n") |
Put that in your path, then in VIM you can run the following command to format the current selection:
Logging SQL statistics in rails
When your sysadmin comes to you whinging with a valid concern that your app is reading 60 gazillion records from the DB, you kinda wish you had a bit more information than % time spent in the DB. So I wrote a plugin that counts both the number of selects/updates/inserts/deletes and also the number of records affected.
1 |
git clone git://github.com/xaviershay/sql-counter.git vendor/plugins/sql_counter |
That does the counting, you need to decide how to log it. I am personally quite partial to adding it to the request log line, thus getting stats per request:
1 2 3 4 5 |
# vendor/rails/actionpack/lib/action_controller/benchmarking.rb:75 log_message << " | Select Records: #{ActiveRecord::Base.connection.select_record_count}" log_message << " | Selects: #{ActiveRecord::Base.connection.select_count}" ActiveRecord::Base.connection.reset_counters! |
Don’t forget the last line, otherwise you get cumulative numbers. That may be handy, but I doubt it. We’re only logging selects because that’s all we care about at the moment. I am sure this will change in time.
UPDATE: Moved to github, bzr repo is no longer available
