Archive for the ‘ruby’ tag
Two years with Ruby
Tonight I realized that its now been two years since I first started working with Ruby. Back in February of 2008, I’d posted about my first experiments with Rails. Lo and behold, its now been two years. I do say two years with Ruby though, not Rails. I’ve been using Merb for about the past year and a half. Although I have started messing around with Rails 3 and really like what I see so far.
So in the past two years, what have I done? Sadly, a lot of it is sorely behind and out dated, but take a bit of a trip down memory lane…
- The SkitchDav I originally blogged about, but now no longer use
- DM-DBSlayer. I was using it in a personal app, but the app didn’t need scale, so eventually removed it.
- DM-Paperclip. I really need to dust this off, it needs some TLC.
- Some hacking on Warehouse, which eventually led to my ideas for Trunks.
- Branches, which was basically a ruby port of Gitosis. I wrote it anticipating using it for Trunks so I could interact with the DB in Ruby. I did in the first incarnation of trunks, but am now on “Branches 2.0″ which was done from scratch and super simplified so it is Trunks-only and structured to do both Git and Mercurial. Would actually be nice to pull it out of Trunks and release it. Damn, another project for my list.
- Tigger. Really bad name. When I first wrote Trunks but was unsure about launching it, tried to redo the interface as a better read-only web interface to git repos. The app should still work… was on Merb 1.0.
- And of course, Trunks. Merb 1.1-pre.
And I’ve also had several apps that haven’t seen the light of day and were more for my personal use or tinkering.
- For a while I was into EVE Online and wrote an app to parse and process item reports from Eve Central. It allowed me to locate items that were selling under market rate quickly, compare prices across markets, and I could somewhat defer what items were selling most at different market hubs. Really geeky. Probably put several months into it. Merb app.
- Also wrote an app to track my sales by location, by item, and comparing sales volumes based on price as I played with prices. Merb app.
- And another for EVE Online… came up with the “Jita Mineral Index” which tracked mineral prices in the main market hub in the game. I left it up for several months after I stopped playing, until I found that the app that processed the emails from Eve Central broke and had like 200k+ unprocessed messages in its inbox. Merb app.
- Some time ago, was playing with different virtualization APIs and wrote an app that would provision and deploy virtual servers in KVM and in XenServer. I’m probably too much of a nut about virtualization. Mixture of stuff… Ruby, Nanite, Merb.
- A really basic Sinatra app to handle my domains that just have static pages. Rather than having multiple sites, I have just one and it determines which template to render based on the host header. Was my first Sinatra app.
And of course some other stuff on Github and Gist.
And I have some that will likely be coming soon:
- This, which I’ll likely blog about once I get a name for it and a domain. And update the incorrect launch date in the News part, oops.
- Was actually thinking of dusting off my original SkitchDav as a Sinatra app. What triggered me to write this.
- Also thinking of dusting off an app I prototyped a while ago involving Rack and Solr.
- Always got ideas brewing in my head… just a matter of parsing out which are really worth my free time.
All this Ruby has also helped me to grow so much more as a .NET developer.
- More languages you know the better.
- Exposure to new methodologies and mindsets, new more progressive community.
- Exposure to testing. Testing is far easier in dynamic languages, and grasping the concepts in a dynamic language makes it easier to apply to a static language.
- MVC. Thank god for MVC. Unlearned all the webforms crap and re-learned plain and simple HTTP and a logical structure for your app.
- Making programming fun again. Its nothing against .NET, but sometimes you need to break out from the familiar and learn something new to remind you why you love programming. Before Ruby, I was stuck in what I knew and don’t really think I was coding for fun much.
Named many-to-many relationships in DataMapper
When implementing the collaborators feature in Trunks, ran into a bit of a roadblock with how to describe the relationships between the repository and the users.
Trunks is built on Merb and DataMapper, and the implementation involved two models: a User and a Project. In Trunks, a repository is a Project because “repository” is a reserved attribute in DataMapper and I was running into issues early on, so I changed it to Project.
A project belongs to a user, and a user has many projects. A user can also be a collaborator on many projects, and a project can have many collaborators. So I already had the first relationship which was a simple 1-to-many relationship. The problem came about with adding collaborators. In a sense, it is a normal many-to-many, which you can do in DataMapper similar to how you would in ActiveRecord using a “has_many …, :through => …” relationship, but I was creating a relationship between models that had an existing and separate relationship.
I didn’t want the user.projects collection to return the projects a user collaborated on, because it was intended to only be the user’s own projects. And similarly, a project only has one owner, not many.
I essentially found I needed a “has many through” with named attributes. To do this, I had to actually create the proxy class that goes between the user and the project model, rather than let DataMapper create it automatically. That way, I can control the attribute names on the proxy class so that “users” and “projects” on the two models won’t get munged.
The result looked like this:
class User include DataMapper::Resource ... has n, :collaborations has n, :collab_projects, :model => 'Project', :child_key => [:id], :parent_key => [:user_id], :through => :collaborations end class Project include DataMapper::Resource ... has n, :collaborations has n, :collab_users, :model => 'User', :child_key => [:id], :parent_key => [:project_id], :through => :collaborations end class Collaboration include DataMapper::Resource property :id, Serial belongs_to :collab_user, :model => 'User', :child_key => [:user_id] belongs_to :collab_project, :model => 'Project', :child_key => [:project_id] end
Having a “collab_users” on the Collaboration class allowed me to have the project model to get an attribute name “collab_users” instead of trying to use “users”. And on user, I could have “collab_projects” instead of mixing things up with the existing “projects” attribute.
When creating the relationships though, I had to be careful to spell out the actual parent and child fields rather than let them be automatic, otherwise it’ll run into issues where it’ll try to create ‘collab_user_id’ and ‘collab_project_id’ and inserts will fail because those columns aren’t being set.
Take the relationship definition on the user model:
has n, :collab_projects, :model => 'Project', :child_key => [:id], :parent_key => [:user_id], :through => :collaborations
I am essentially saying create an attribute named ‘collab_projects’ through the ‘collaborations’ relationship. ‘collab_projects’ will be of type Project. The child key on Collaboration is ‘user_id’ and the primary key on User is ‘id’. Here, the fields I specify are betwen User and Collaboration, not on Project.
Then on Collaboration, I have:
belongs_to :collab_user, :model => 'User', :child_key => [:user_id]
Here I tell it that the attribute on Collaboration will be collab_user, but the model it is linking to is User. Basically saying I belong to user, but don’t create an attribute named ‘user’. I set the child key, which is the column on Collaboration, to be ‘user_id’. I want to do that else it will try to create it as ‘collab_user_id’.
Confused enough? All boils down to having two separate relationships between two models and having control over what the attributes are named. After all this, I end up with these calls:
user.projects # the user's own projects user.collab_projects # the projects the user is a collaborator on project.user # the owner of the project project.collab_users # the users who are collaborators on the project