Optional method parameters in Ruby
One of the things I love about Ruby is the flexibility it provides when it comes to method parameters. It’s dead simple to provide default values for one or more parameters, to handle variable length argument lists, and to pass blocks of code into a method. But perhaps my favorite is the ability to tack hash key/value pairs onto the end of a method call, and have those options combined into a Hash on the other side.
def some_method(required_1, required_2, options={})
# Do something awesome!
end
some_method("foo", "bar")
some_method("foo", "bar", :option_1 => false, :option_2 => true)
some_method("foo",
"bar",
:option_1 => false,
:option_2 => true,
:option_3 => "something",
:option_4 => "something else")
This may not look like much. However, this feature alone is capable of producing some very readable code, and is used extensively in APIs throughout the Ruby ecosystem. Consider for a moment what these APIs would look like if Ruby did not have this capability, which isn’t hard to imagine for those of us with a background in a language like Java. You would either be forced to require that each parameter be specified:
# What is this code doing? What do the nil values,
# or even the true and false values map to?
some_method("foo", "bar", false, nil, true, nil)
or accept a hash or a request object that contains all of the necessary parameters:
# This is much more readable, but requires that the
# options hash be created on its own line.
options = {:option_1 => true, :option_2 => false}
some_method("foo", "bar", options)
Providing optional parameters via hash key/value paris at the end of a method call produces code that is incredibly readable. You have the names of the attributes right next to their corresponding values! There is no ambiguity whatsoever as to which values match up with which parameters.
It is also very flexible. The order of the attributes in the hash does not matter, like it does for required attributes. And, it is very easy to add new options, or delete old ones.
This approach also makes it easy to specify default values for options that were not specified when calling the method:
def some_method(required_1, required_2, options={})
defaults = {
:option_1 => "option 1 default",
:option_2 => "option 2 default",
:option_3 => "option 3 default",
:option_4 => "option 4 default"
}
options = defaults.merge(options)
# Do something awesome!
end
There are however a few minor drawbacks to this approach. The first is documentation. Methods that take a hash of options as a parameter do not convey any information about the valid options in the method definition alone. And, it is possible that the method in question simply forwards the options to another method, sending you on a wild goose chase to determine the set of valid options the code supports.
# Looking for a list of valid option keys...no help here.
def some_method(required_1, required_2, options={})
do_something_awesome_with_the_options(options)
end
This is why it is so important do document your public API if you are using this approach. Take a look at the ActiveRecord::Associations::ClassMethods documentation. This page documents, in a very clear and easy to read mannor, all of the supported options for each method.
It is also worth pointing out that while this approach is great for optional parameters, it is ill suited for required parameters. Required parameters should be specified outside of the options hash, making it clear that values for the required parameters must be provided. While it’s true that stuffing all of your parameters inside a hash means you’ll never have to look at another wrong number of arguments error again, it will make your code difficult to understand, and easy to misuse.
Introducing Tenacity – An ORM Independent Way to Manage Inter-database Relationships
I’m a big believer in polyglot persistence. There are so many (very different) production ready databases available today that’s it is becoming more and more common to find applications using more than one database, utilizing the strengths of each. Using the right tool for the job gives me a warm, fuzzy feeling inside.
However, polyglot persistence comes with its own set of drawbacks. One of those drawbacks is the loss of foreign keys, which are very important in maintaining data integrity. Another drawback is that Object/Relational Mapping (ORM) libraries typically focus on a specific database, or type of database. So, writing code that manages relationships between objects backed by different databases hasn’t been nearly as easy as writing code to manage relationships between objects in the same database.
Tenacity’s goal is to address some of these issues. Tenacity is a ruby gem that provides an ORM independent way of managing relationships between models backed by different databases.
Tenacity works by extending popular Ruby ORM libraries to respond to a set of methods that the tenacity core uses to build and manage relationships between objects. By extending the ORM libraries to implement this interface, tenacity is able work with the objects in a generic way, without having to know what database is backing the given objects. This approach also allows you to continue using your favorite ORM libraries. To use tenacity, you simply need to include Tenacity inside your model.
Tenacity is heavily based on ActiveRecord’s associations, and aims to behave in much the same way, supporting many of the same options.
This initial release of tenacity supports belongs_to, has_one, and has_many associations, and the ActiveRecord, CouchRest, and MongoMapper ORMs. However, there is still plenty of work to be done. Feedback, bug reports, and code contributions are always welcome.
Tenacity is free and open source, and can be found on GitHub at https://github.com/jwood/tenacity.
Example
Download
gem install tenacity
Trastel Accepted as Official Service Level Testing Tool @ Orbitz
Trastel, the DSL I created to help with the automated testing of our services at Orbitz, has been accepted as the QA team’s official testing language. Yippee!
Addressbook Webapp Has Been Released
Yay for me! I set a personal goal to have the code for all of my Rails apps on this site by the end of the year. Tonight, I can check that one off the list. The code for the Addressbook webapp is now available for download. Addressbook was not only my first Rails app, but also my first experience with Ajax. And trust me, it shows. I learned a lot from this project, especially what not to do. However, I must also say that I use Addressbook more than any other personal project I have ever completed. So, it can’t be that bad :) I love the fact that I can access my contact information from anywhere, and that I can manage groups of addresses and print mailing labels with the click of a button. Sure, the UI can be a bit unintuitive, but it’s not that big of an issue for me. There are a few more things I’d like to do with this project. We’ll see where it goes from here.
Falling in Love with DSLs
We were recently given a free day at work to hack on a project that was outside the realm of our normal responsibilities, yet could still be beneficial to the company. We were encouraged to be creative, explore ideas that interested us, and see if we could come up with something to demo at the end of the day.
Service level testing, or functional testing, has been a hot topic at work recently. It’s no secret that we have a very large SOA at Orbitz, powered by Jini. Services, calling services, calling services…you get the picture. Historically, we have not been the best at automating the testing of these services. That is beginning to change. About two years ago, the team I work on developed a test tool that allows us to interact with our services at a very high level, keeping us out of the nitty-gritty details when invoking a service. It’s a command line based tool with a very simple, intuitive syntax. All of the details are accessible if we need them, but more often than not they just get in the way. In fact, we enjoyed working at this level of abstraction so much, that I wrote a functional test framework (named jwoodunit by my co-workers) that drove service level tests against our services, which were written in this same “languageâ€. It allowed us to pump out service level tests is very short order that were easy to read, and easy to maintain. It has only just occurred to me that what we had created was really a Domain Specific Language (DSL).
We have a fairly large quality assurance team that is made up mostly non-developers. Most of the testing that is done is manual, or driven by an automation tool similar to Selenium. The problem is that the manual testing is slow and not reliable, and tools like Selenium tend to be brittle, since they are based on the layout of the HTML page. It also prevents you from testing any service that is not directly accessible through the web application. So, for my project, I wanted to see if I could take my team’s DSL, clean it up even more (it is still very “programmyâ€), and give it to our quality assurance team so that they could test our services in a more reliable fashion.
What I ended up with was a very high level, English like DSL that I call Trastel (Travel Service Testing Language). Trastel is implemented in Ruby. In fact, it’s safe to say that Trastel is Ruby. I didn’t implement a new language. I simply took advantage of Ruby’s fantastic meta programming capabilities to extend the language with functionality that is needed by the tests. A example test is worth 1000 words:
search.flights.on("orbitz").where(
:origin => "ORD",
:destination => "LAX",
:departure_date => "2008/12/10",
:return_date => "2008/12/15"
)
verify_at_least_one_result
foreach_result do
verify_equal "ORD", origin
end
This does exactly what you’d expect. It searches flights on Orbitz, flying from Chicago’s O’Hare airport to Los Angeles International Airport on 2008/12/10, returning on 2008/12/15. We then verify at least one result came back, and that the origin of each flight is O’Hare. That’s it.
Implementing this test in Ruby was a breeze. Since everything is an object in Ruby, dynamically adding methods to the Object class gives us the ability to create pseudo-keywords, like “searchâ€, and “foreach_resultâ€. Trastel also sets an attribute named @response on the test, so the code that implements the verify methods can just check that, instead of having to specify that your checking the response of the service call. foreach_result will iterate over the @response if it is an array, yielding to the specified code block for each item in that array…giving us an easy way to check each element. The last bit of magic surrounds the “origin†method. “origin†isn’t a method on object. It’s a method on the the type contained in the @response. Thanks to Ruby’s method_missing method, I can forward that method call onto the object in the @response for it to tell me what origin means. Nice and easy.
Another great thing about Trastel is that it sucks in Active Support. Active Support brings with it a wealth of extensions to the core ruby classes, letting us do something like this:
search.flights.on("orbitz").where(
:origin => "ORD",
:destination => "LAX",
:departure_date => 4.weeks.from_now,
:return_date => 6.weeks.from_now
)
We still do all of the heavy lifting (creation of the service request, execution of the remote service, etc) in Java, using JRuby to get the Ruby code to play nice with the Java code. But, the DSL serves as an excellent means to collect the data needed to drive the test, call the Java code to invoke the service with that data, and verify the data in the response matches what was expected. It successfully keeps the person writing the test from having to know anything about actually invoking the service. Writing tests at this level of abstraction also shields us from the majority of service API changes that may come our way.
With all of the benefits that I can already see with this still very immature DSL, I’m kicking myself for not considering DSLs more seriously in the past. They have been around quite some time, and have had many well know developers singing their praises. I’m looking to see what other problems I’m currently facing that could be solved a little easier by letting me code a little closer to the domain. Had I know that it would be this simple, maybe I would have tried it a long time ago.
GitHub
Most Popular Posts
Tags
Archives
- December 2011 (1)
- September 2011 (1)
- July 2011 (1)
- May 2011 (1)
- April 2011 (1)
- March 2011 (1)
- January 2011 (2)
- November 2010 (2)
- September 2010 (1)
- August 2010 (1)
- July 2010 (2)
- June 2010 (2)
- April 2010 (1)
- March 2010 (1)
- February 2010 (2)
- January 2010 (1)
- December 2009 (1)
- November 2009 (1)
- September 2009 (2)
- August 2009 (3)
- July 2009 (2)
- June 2009 (3)
- April 2009 (1)
- February 2009 (1)
- January 2009 (2)
- December 2008 (8)
- November 2008 (2)
- October 2008 (3)
- September 2008 (6)
- July 2008 (3)
- June 2008 (1)
- May 2008 (8)
- April 2008 (6)
- March 2008 (2)
Blogroll
Industury News
Other Links
My GitHub Feed
- jwood pushed to stripe at signal/proby
- jwood pushed to stripe at signal/proby
- jwood pushed to stripe at signal/proby
- jwood pushed to stripe at signal/proby
- jwood pushed to stripe at signal/proby
- jwood pushed to stripe at signal/proby
- jwood pushed to stripe at signal/proby
- jwood pushed to stripe at signal/proby
- jwood pushed to stripe at signal/proby
- jwood pushed to stripe at signal/proby




