-
23
APR
2008The Nice New Italian Restaurant of Server Monitoring
David Heinemeier Hanson, the creator of Rails, has made one of his business strategy talks available recently. This is a great talk about how we all might be trying just a little too hard at what can be a fairly simple task.
We all hear advice like this over and over again, but we seem to forget it so fast that we need that constant reminding. Just today I saw a site redesigned to improve a section users haven't even seen yet (as that section isn't yet public). Is that really the top use of resources for an unlaunched portion of the site? How do we know they wouldn't have liked it? How do we know they will like the new version better? I'm sure it won't surprise readers to learn that this site is over budget on time and money.
The point of all of this is that I want to tell you about a new server monitoring tool the company I work for has recently launched. The new service is called Scout and we've done our dead-level best to keep to the simplicity principal both because we agree with David and because we flat out need it to work that way.
-
10
APR
2008Five ActiveRecord Tips
This article was written for the Railcasts 100th Episode Contest. I think the idea is great and I look forward to reading great tips from all who decide to participate.
1.
create_or_find_by_…
I imagine most of you know that
ActiveRecord
can handle finders like:MyARClass.find_or_create_by_name(some_name)
This will attempt to find the object that has
some_name
in itsname
field or, if the find fails, a new object will be created with thatname
. It's important to note that the order is exactly as I just listed it: find then create. Here are the relevant lines from the current Rails source showing the process:record = find_initial(options) if record.nil? record = self.new { |r| r.send(:attributes=, attributes, guard_protected_attributes) } #{'yield(record) if block_given?'} #{'record.save' if instantiator == :create} record else record end
The above code is inside a
String
literal fed toclass_eval()
, which is why you see interpolation being used.Unfortunately, this process is subject to race conditions because the object could be created by another process (or
Thread
) between the find and the creation. If that happens, you are likely to run into another hardship in that calls tocreate()
fail quietly (returning the unsaved object). These are some pretty rare happenings for sure, but they can be avoided under certain conditions. -
18
MAR
2008Death and Spam
The popular expression warns us that death and taxes are the only two certainties, but I'm worried we may need to add spam to the list. Publish any material that draws readership on the Web and invites reader feedback and you can be certain sure you will be swimming in spam soon enough.
One of the biggest reasons I switched to my own blogging engine was to fine tune my spam control strategy. Until today, the system was that I received an email anytime a comment was posted to this blog and it was a single keystroke to remove any offensive content. While that was as simple as I can think to make a process, the fact was that it still wasn't good enough.
The spammers ramped up their efforts until I was facing about 50 useless posts every 12 hours. While I didn't mind clearing them, the fact was that visitors were probably seeing spam due to the regularity of the postings and the time between the post and my clearing it. Obviously, there was at least spam on the blog while I slept each night.
-
1
MAR
2008Design Patterns in Ruby
I've been lucky enough to read a string of good Ruby books lately and the latest in that line is Design Patterns in Ruby. This book attempts to modernize the software design patterns by showing how these patterns can be applied in a language as dynamic as Ruby. Despite a couple of minor missteps along the way, the book definitely delivers on this goal.
Design Patterns in Ruby begins with a couple of introductory chapters introducing both the concepts behind reusable software patterns and the Ruby programming language. After that, the main section of the book has 13 chapters that walk through 14 of the patterns first introduced in the famous "Gang of Four" book that began the design patterns movement. The author then introduces three new patterns he feels have grown out of day to day Ruby usage. Finally the book closes with a short conclusion and two appendices on installing Ruby and other sources of information.
Most of that content is exactly what I wanted to find in this book, but you're going to have to tolerate a diversion about a pet peeve of mine that is strangely common in Ruby books. This is really, really important, so listen up: you can't teach Ruby in 30 pages. Period. I can hear all on the "But…" replies forming out there now… No buts. You can't. Trust me. Not even if the person is already an experienced programmer. It just can't be done. Authors and publishers need to come to terms with that and move on. Its just fine to say, "This is not a beginning Ruby book." We even prefer that, because we know when we are ready to read it and we don't have to skip that useless 30 page chapter when we do. Please, stop including introductory Ruby chapters in every book.
-
29
FEB
2008A Bug, Today Only
I had to debug some tests that just started failing first thing this morning. I guess I should have procrastinated though, because they would have magically fixed themselves tomorrow morning. The cause of the one day only bug: Leap Year Day, of course.
If you run the following code on a day like today, February 29th 2008,
Date
will choke on your invalid date:require "date" class Date # Returns a Date in the past +year_offset+ years ago. def self.years_ago(year_offset) now = today Date.civil(now.year - year_offset, now.month, now.day) end end puts Date.years_ago(1)
I came up with the following fix, which is accurate enough for my purposes:
require "date" class Date # Returns a Date in the past +year_offset+ years ago. def self.years_ago(year_offset) today - 365 * year_offset end end puts Date.years_ago(1)
I'm not 100% sure that covers all cases though, so use with caution. Date's are tricky business!
-
28
JAN
2008Practical Ruby Projects
Practical Ruby Projects is a pretty poorly named title, but, luckily, that doesn't stop it from being a very strong book. The book actually turns out to be an exploring-the-Ruby-programming-language-by-example book. These aren't your trivial beginners-only tasks though. There's enough meat in these pages for the intermediate crowd to really get into.
Let me start by clarifying my earlier comment about the title. It's clear this book is named after the series it appears in, instead of the actual content it holds. There are lots of projects in the book and they are definitely written in Ruby, but Practical is not the word I would use to describe them. Fun, on the other hand, would be a great word. Beyond that, the code and concepts used in these projects is well worth studying. Just don't expect to find the typical (for Ruby) collection of Web programming tips inside. To me, that was a big plus. The title just misrepresents what's inside.
The projects you will find in the book include: MIDI music generation, SVG graphic building, pocket change simulations, a turn-based strategy game, a Mac OS X GUI, a genetic algorithms framework, as well as both a parser and interpreter for the Lisp programming language. While these projects obviously tackle subsets of each problem space, they go deep enough to serve as a solid introduction in each area. The author is also good at focusing on the more interesting aspects of each challenge and throwing in a few twists to keep your interests high.
-
24
JAN
2008From Bash to Z Shell
I work on multiple Unix platforms all day long. I had never really taken the time to learn about the shells, but I had picked up the basics over time. I knew how to run commands, string them together with pipes, and redirect their output into files. So when I tell you that I started learning new things in the first chapter of From Bash to Z Shell, you will know the coverage is in depth. If you are a casual shell user, or even less experienced, this book has a lot to offer you.
From Bash to Z Shell is organized into three parts. Part one is an introduction to shell basics. It focuses on typical interactions with a shell including all of the things I mentioned knowing before reading this book. There is surprisingly good depth even here though and I doubt that anyone short of a power user could make it through this section without picking up a new trick or two. I learned multiple things from each chapter in this section.
In part two, each chapter takes a single aspect of the shells and really focuses in on just that. You will find chapters about the startup files each shell invokes as well as shell command histories. This is comprehensive coverage that really gets you to understand how things work as well as how to tune them to your personal tastes. You are even less likely to not pickup great tips in here.
-
4
JAN
2008Dave Thomas is Definitely the Sammy Sosa of Programming
There is a debate raging in the Ruby community that I don't want any part of. I'm not going to engage in any of the mud slinging and there will be no debate here. Commenters have been warned. What I do want to do is to share some simple uncontested facts about a man I am lucky enough to know.
Sammy Sosa is famous for one thing: hitting home runs. In the entire history of the game of baseball five players have managed to hit over 600 home runs and Sammy is one of them. If that wasn't amazing enough, he has hit at least one home run against every single Major League team and in 44 Major League ballparks. Baseball fans everywhere love to watch Sammy Sosa at bat.
Now if I had to name five programmers who get me as excited about programming as Sammy Sosa does about baseball, Dave Thomas would definitely make the list. Dave does exactly what Sammy always does: continually preforms the hardest tasks of his profession while making it look easy to the fans. Allow me to give a few examples.
-
2
JAN
2008Getting FasterCSV Ready for Ruby 1.9
The call came down from on high just before the Ruby 1.9 release: replace the standard
csv.rb
library withfaster_csv.rb
. With only hours to make the change it was a little harder than I expected. TheFasterCSV
code base was pretty vanilla Ruby, but it required more work than I would have guessed to get running on Ruby 1.9. Let me share a few of the tips I learned while doctoring the code in the hope that it will help others get their code ready for Ruby 1.9.Ruby's
String
Class Grows UpOne of the biggest changes in Ruby 1.9 is the addition of m17n (multilingualization). This means that Ruby's Strings are now encoding aware and we must clarify in our code if we are working with bytes, characters, or lines.
This is a good change, but the odds are that most of us have lazily used the old way to our advantage in the past. If you've ever written code like:
lines = str.to_a
you have bad habits to break. I sure did. Under Ruby 1.9 that code would translate to:
lines = str.lines.to_a
-
18
NOV
2007Ghost Wheel Example
There has been a fair bit of buzz around the Treetop parser in the Ruby community lately. Part of that is fueled by the nice screencast that shows off how to use the parser generator.
It doesn't get talked about as much, but I wrote a parser generator too, called Ghost Wheel. Probably the main reason Ghost Wheel doesn't receive much attention yet is that I have been slow in getting the documentation written. Given that, I thought I would show how the code built in the Treetop screencast translates to Ghost Wheel:
#!/usr/bin/env ruby -wKU require "rubygems" require "ghost_wheel" # define a parser using Ghost Wheel's Ruby DSL RubyParser = GhostWheel.build_parser do rule( :additive, alt( seq( :multiplicative, :space, :additive_op, :space, :additive ) { |add| add[0].send(add[2], add[-1])}, :multiplicative ) ) rule(:additive_op, alt("+", "-")) rule( :multiplicative, alt( seq( :primary, :space, :multiplicative_op, :space, :multiplicative ) { |mul| mul[0].send(mul[2], mul[-1])}, :primary ) ) rule(:multiplicative_op, alt("*", "/")) rule(:primary, alt(:parenthized_additive, :number)) rule( :parenthized_additive, seq("(", :space, :additive, :space, ")") { |par| par[2] } ) rule(:number, /[1-9][0-9]*|0/) { |n| Integer(n) } rule(:space, /\s*/) parser(:exp, seq(:additive, eof) { |e| e[0] }) end # define a parser using Ghost Wheel's grammar syntax GrammarParser = GhostWheel.build_parser %q{ additive = multiplicative space additive_op space additive { ast[0].send(ast[2], ast[-1]) } | multiplicative additive_op = "+" | "-" multiplicative = primary space multiplicative_op space multiplicative { ast[0].send(ast[2], ast[-1])} | primary multiplicative_op = "*" | "/" primary = parenthized_additive | number parenthized_additive = "(" space additive space ")" { ast[2] } number = /[1-9][0-9]*|0/ { Integer(ast) } space = /\s*/ exp := additive EOF { ast[0] } } if __FILE__ == $PROGRAM_NAME require "test/unit" class TestArithmetic < Test::Unit::TestCase def test_paring_numbers assert_parses "0" assert_parses "1" assert_parses "123" assert_does_not_parse "01" end def test_parsing_multiplicative assert_parses "1*2" assert_parses "1 * 2" assert_parses "1/2" assert_parses "1 / 2" end def test_parsing_additive assert_parses "1+2" assert_parses "1 + 2" assert_parses "1-2" assert_parses "1 - 2" assert_parses "1*2 + 3 * 4" end def test_parsing_parenthized_expressions assert_parses "1 * (2 + 3) * 4" end def test_parse_results assert_correct_result "0" assert_correct_result "1" assert_correct_result "123" assert_correct_result "1*2" assert_correct_result "1 * 2" assert_correct_result "1/2" assert_correct_result "1 / 2" assert_correct_result "1+2" assert_correct_result "1 + 2" assert_correct_result "1-2" assert_correct_result "1 - 2" assert_correct_result "1*2 + 3 * 4" assert_correct_result "1 * (2 + 3) * 4" end private PARSERS = [RubyParser, GrammarParser] def assert_parses(input) PARSERS.each do |parser| assert_nothing_raised(GhostWheel::FailedParseError) do parser.parse(input) end end end def assert_does_not_parse(input) PARSERS.each do |parser| assert_raises(GhostWheel::FailedParseError) { parser.parse(input) } end end def assert_correct_result(input) PARSERS.each { |parser| assert_equal(eval(input), parser.parse(input)) } end end end