Major Ruby OData Update v0.1.0
It’s here! I’m happy to announce a major release of ruby_odata, v0.1.0. There are many enhancements found in this release so let’s dig in and see what’s new.
There is one thing that we need to get out of the way first, a breaking change. In v0.0.10 and below, queries and some instances of calls to save_changes
would return a single entity if there was only one returned. This was confusing and could lead to errors. As a consumer of the gem, you wouldn’t know if a call to the Service’s execute
method was a single object or an enumerable. Instead of you having to guess, those calls now return enumerables all the time. This applies to calls to execute
and save_change
additions. Updates, deletes, and add_link
calls (we’ll get to those in a minute), as well as batch saves all return Booleans, but single save_change
add calls will return an enumerable because those results are parsed similar to execute
calls.
Now that that’s out of the way, let’s look at the new features.
The Features
Partial Results
The first change is thanks to arienmalec. OData allows services to do server-side paging in Atom by defining a next link. The default behavior is to repeatedly consume partial feeds until the result set is complete, for example:
svc.Partials
results = svc.execute # => retrieves all results in the Partials collection
If desired (e.g., because the result set is too large to fit in memory), explicit traversal of partial results can be requested via options:
svc = OData::Service.new "http://example.com/Example.svc", { :eager_partial => false }
svc.Partials
results = svc.execute # => retrieves the first set of results returned by the server
if svc.partial? # => true if the last result set was a partial result set (i.e., had a next link)
results.concat svc.next # => retrieves the next set of results
end
while svc.partial? # => to retrieve all partial result sets
results.concat svc.next
end
Single Layer Inheritance
This feature was long overdue! Back in July 2010, Scott Allen wrote a post about ruby_odata where he mentioned a change to the Service class that would parse models with single layer inheritance. His code has finally been integrated into ruby_odata. Note, that the addition currently only support single layer inheritance, but that should handle most of the cases you will encounter.
Querying Links
There are times when you don’t necessarily want to bring down full navigation property entities. In these cases the OData protocol has support for Addressing Links Between Entities. To support this, there is a new method that can be tacked on to a single entity query (e.g. finding an entity by id) called links
where you pass the name of the navigation property of the links that you want to retrieve. For example:
svc.Categories(1).links("Products")
product_links = svc.execute # => returns URIs for the products
Add Link
There are times when you need to create a linkage between a parent and a child, and you don’t want be required to perform an add or update. You can now simply call the add_link
method on the OData::Service
with the parent object, the name of the navigation property, and the child object. Finally, call save_changes
. For example:
svc.add_link(<Parent>, <Navigation Property Name>, <Child>)
svc.save_changes
Lazy Loading
In previous versions of ruby_odata, if you wanted to perform lazy loading of navigation properties you would need to do it manually, which was extremely ugly. I’m happy to say that there is now a way to perform lazy loading in a graceful way. All you need to do is to call the load_property
method on the OData::Service
with the entity to fill and the name of the navigation property. For example:
# Without expanding the query
svc.Products(1)
prod1 = svc.execute.first
# Use load_property for lazy loading
svc.load_property(prod1, "Category")
Reflection / Introspection
Paul Gallagher (tardate) had a great suggestion: Why should you have to look at the EDMX metadata file directly when ruby_odata parses all that data? You can now use the OData::Service
to access a list of the collections
that the service exposes. There is also a classes
method, which gives you a list of the classes generated by the OData::Service. A function_imports
method, which will list function imports exposed by the service (we’ll get to those in a minute). On a class, you can call the class method, properties
, which as you guessed will give you information about each property. Each of these methods return hashes where the keys are the name, and the values are hashes of the metadata for the member that you are introspecting.
Function Imports
Quite a while ago, I received an message from a user via this blog asking about the ability to call custom service methods. Within a WCF Data Service, you can expose your own custom methods, which get translated into the EDMX as FunctionImports. These aren’t to be confused with Entity Framework’s FunctionImports
. These will be parsed and dynamically added as methods to the OData::Service
. When you call one of the methods, they will return a result without you needing to make a call to execute
or save_changes
. There are different result types. You may get back a true if the FunctionImport
doesn’t return any content (for example, a delete). You may get back a single entity, or a collection of entities. Finally, you may get back a single primitive value (e.g. Integer, String, etc), or a collection of primitive values. You can use the function_imports
method on the Service to determine the type that will be returned.
Namespaces
In the same message as the request for the custom service methods (ok, it was actually a reply), the user mentioned that he received type conflicts with the generated classes if one by the same name already existed. If this is a problem for you, you now have the ability to tell the OData::Service
to generate the models in a specific namespace. There is a new option that can be passed in to the constructor’s option hash, :namespace
. If you use this option, there shouldn’t be any collisions. You can pass a namespace in either Ruby (with double colons, e.g. RubyOData::Rocks
) or .NET style (with periods, e.g. RubyOData.Rocks
) if there is more than one part to the namespace that you want to use. If you don’t provide a namespace, then ruby_odata will generate the classes without any namespace just as it has before.
The Beginnings of Something Cool?
As developers, the majority of the time we either retrieve collections or a single entity, usually by an ID. I didn’t really like the current steps to retrieve a single entity by ID:
svc.Categories(1)
category = svc.execute.first
Sort of ugly, right? So inspired by Ruby on Rails, I added a class method for each created class so that you could just do this:
category = Category.first(1)
Great, right? It’s a little nicer, but sadly it’s a one trick pony right now. There’s no filtering, eager loading, etc. My idea was to hopefully expand on the concept like Rails and ActiveRecord, but for now it’s just an easier, cleaner way to get an entity.
Testing
In addition to the new features, I’ve been busy working on the testing suite.
No More Hardcoded URIs
Having to rely on Windows for testing has made me stick to Windows and e-TextEditor for ruby_odata development. The key word there is actually testing. All that Windows is required for is hosting the WCF Data Service for the Cucumber integration tests, but Cucumber is just one part of it, RSpec is the other. It’s no denying that Ruby on Windows is slower than its *nix counterparts (not to knock the RubyInstaller.com team, if it wasn’t for them, Ruby on Windows would be non-existent). Where you really see a slowdown is when you are doing TDD/BDD testing. You need tests to run fast, and if you ran the command time rake spec
, my Windows box is more than 4 times slower than my old MacBook (note, not a Pro), which has similar hardware. Now, multiply that by every spec I write and I want to throw my machine out a window. So, in an effort to migrate development, the URLs and ports required for the sample WCF service are now dynamic. Take a look at features/support/constants.rb for more information. Thanks to Sean Carpenter (scarpenter) for his fork where he changed the static values in that file to be optionally set by environment variables.
Hello New Technology
I decided to change up the technologies used within the test service. First, no more SQL Express, hello SQL Compact 4. Why the change? SQL Compact 4 is simply DLL deployed, you don’t need to install anything, cool. Also, since SQL Compact 4 databases are file based, instead of having to truncate to clean the database for testing, the file could just be blown away and rebuilt easily.
So, a new database, how about a new ORM? Entity Framework 4.1, also known as “magic unicorn edition” is now being used. EF 4.1 “Code First” as it is called, is just awesome. Ditching Entity Framework’s EDMX for pure code, now that is right up my alley.
Finally, since I switch to EF 4.1, the current released version of WCF Data Services, well, didn’t work right. You know what did? The WCF Data Services October 2011 CTP, w00t.
Everything you need should be in the new test project (now called RubyODataService, changed from SampleService, though that’s still in the test URLs) thanks to NuGet and my copying of the WCF DS CTP DLLs to the packages directory since there wasn’t a NuGet package for it).
Pickle
I love the pickle gem. Aside from being authored by a guy with my last name (no relation), pickle makes Cucumber features so much nicer to read. Throughout development of ruby_odata, I’ve written my own step definitions to do things, essentially cloning what pickle does out-of-the-box, e.g. creating models, asserting against models, etc. I had toyed with getting pickle to work around June of last year, but was having issues. As time passed and experience grew, I finally got it all working. I used it in a few places in new tests, but I hope to do a complete overhaul. My pickle adapter also needs a bit of work since I didn’t put it through all of its paces, and some things need to be added to ruby_odata before it will function as expected. All in all though, it is great to finally have pickle working.
Conclusion
I hope that you find the new version of ruby_odata to be useful. In the next version, I hope to further enhance the gem with more of the different operations that OData can perform. I’m sure I’ll perform some more housekeeping in the test suite removing my ugly Cucumber steps for nice, clean Pickle steps.
What would you like to see in the new version? Add your suggestions to the issues section on GitHub. I’d also like to know what you think of the API as it stands right now. I’ve essentially been following the OData Silverlight client API syntax (e.g. AddTo