Like some other folks working with Rails, I've been a bit frustrated with Fixtures. Foxy Fixtures, Rathole, and such things, including what is in Rails 2, have helped a lot. However, the two biggest frustrations for me come down to the fragility of fixtures, and knowing what fixtures you have and how they relate to their associated fixtures. I would find myself thinking, "hmm, which user do I use when I want the one with X associations" or what not. Naming your fixtures well helps, but only so much.
Lately, I've come across two plugins that I am really loving. These are Shoulda and object_daddy. Shoulda seems to be gaining in popularity in the Rails community, which doesn't surprise me. It gives you some of the best syntax of RSpec, without having to use RSpec (which I am not all in love with, unlike various others), as well as it gives you some nice "should" methods, and other features I'll get into in a minute.
object_daddy I think is rather obscure. I only found out about it due to Tammer Saleh's presentation (video link) on Shoulda from the 2008 MountainWest RubyConf. object_daddy has improved in its short life, and is quite useful today. What it does, is provide a factory/generator mechanism for creating model objects. I've done this in the past with object constructors or factories, etc., but object_daddy organizes all this, and provides a slick mechanism, called "exemplars", that specify how model attributes are defined when generating objects, and more importantly, when generating multiple objects. Tammer covers this issue in his presentation, which I highly recommend watching.
It's taken me a bit of time/use of Shoulda to get really into it, but like so many things, use it a bit and then the light bulb not only goes off, but seems to erupt with light. The big one here for me was how to leverage contexts, and by that I really mean nested contexts. My tests now not only read better, but can be written in a much nicer fashion, as well as organized in a great way. On the organization front for example, I now often have two top level contexts in a functional test: one for cases where I'm testing actions without a user being logged in, and the other for when a user is logged in. Great way to group them.
But, what's got me most excited lately is the combination of these two testing tools, and what it's done to my tests. First, I've darn near eliminated fixtures on the project I'm using this most with so far. This has removed the fragility, as well as it's just FAR easier to understand a test case's setup/scenario. I use Shoulda's contexts and setup, combined with generated objects from object_daddy to create "scenarios" (to steal the term from the plugin that provides this kind of thing for fixtures). The benefit is that you have all of the info about your test right there in one place in front of you. You don't need to bounce between likely multiple fixture files and your test code file to ascertain what's being used in your test. Plus, you can be very specific about the data being used in that particular test (or tests).
I heartily recommend you try this out. Two other things of note:
- Shoulda can be mixed in with existing TestUnit. So, you can slowly convert to it, or just build new tests with it, etc. Very nice. And, it doesn't require anything special to run the tests (it really is just a method generator for building TestUnit tests).
- Check out Shoulda's "should_eventually" method. I'm making more and more use of this, as I use a Test First approach. So, I go in, and build lots of tests, and do a lot of "should_eventually" as I think of things to test and functionality I need, etc. Then as I determine how to write those tests, and following on from that, write the implementation, you simply remove the "_eventually" and let it rip.
Note, if you are an RSpec fan, you can of course achieve the same as Shoulda for contexts and such (I think anyway), so just pull in object_daddy and leverage that aspect.
And last, but not least, I've forked object_daddy to make one tiny change (a single line, actually, a single method call name change!) that's made a big difference for me (comments very welcome). This change is to, by default, call
create
in the generate
method that object_daddy adds to your ActiveRecord objects, instead of calling new
. This avoids what I found to be the common case of doing:
my_new_object = SomeModel.generate
my_new_object.save
my_new_object # or some use of it
Now, you can simply call
SomeModel.generate
, and use that inline, knowing it's saved in the DB, etc. I want to take a look at adding options to generate, or additional generate methods that provide the flexibility to use new, or create! or such things, for the cases where those are needed. My fork is hosted on GitHub, and is public, so feel free to check it out: http://github.com/chris/object_daddy.p.s. For those that wonder why I've "all but eliminated fixtures", as in, what do I still have in fixtures? The only things are standard data, which in my case amounts to a couple specific users, and a couple of Roles and Permissions. These normally get setup in the DB migrations, but I'm working through what Rails does when you run tests and it wipes the DB clean (and thus doesn't pick up seed data from migrations), and other such issues.
32 comments:
Thanks much for the info on this subject, Chris. It's very timely for me: I was just about to jump on the RSpec bandwagon, in spite of liking the look of Shoulda better. I think I'll take Shoulda for a ride, now. I'd not heard of Object Daddy before. Looks real promising. I'd be interested to hear more about your experiences using it.
Hey Trent, hope all is well (you're still at Adobe right?). Anyway, ya, Shoulda is great, definitely worth looking into.
object_daddy is excellent. There are a few surprises (well, I think) here and there when writing generators, and their docs don't match up to how it works quite (the main reason I made my fork and fixed/changed the generate method to call create instead of new; so you can actually write code like they show in their docs :)
One caveat I just ran into, which makes sense in hindsight, is, don't use a method call as a value for a generator. For example, if you had:
generator_for :user => User.generate
That will not work properly. Instead, you want to do:
generator_for :user do
User.generate
end
Feel free to ping me more on it if you like. Will be interested to see how you like it, and how others are using it, etc.
this site is just what i need some good info on the code
Object_daddy looks real complicated. I think it should be simplified, it has a HUGE learning Curve.
My Site
This would help my friends a lot..=)
Very helpful posts...good choice of your blog title :)
@mental it's actually not complicated. Take a look at the docs and such. Basically, all you have to do to use it at the most basic is to install the plugin, and then call YourModelObject.generate whenever you need to make an object of that type. Usually you're going to want to customize how the generate works though, and I personally prefer to use "exemplars" for that, so as to keep that code separate from my main model definitions.
To use exemplars, it amounts to creating a new file in the test/exemplars directory, and then just adding lines like:
YourModelObject.generator_for :user do
User.generate
end
Or something like:
YourModelObject.generator_for :weight do |prev|
prev.succ
end
Usually your generators can be that simple, sometimes even simpler if you just want the same value all the time, you can do:
YourModelObject.generator_for :weight => 100
Try it out, it's actually quite easy. Even the object_daddy code itself is contained in a single file that is not long.
Thanks for this info. My daughter will be thrilled when I tell her about your blog. It is just what she needs!
Nice one - I ve learnt something today !!
I saw some stuff I understood but for the most part, I didn't understand anything. Well, ok.
i have learnt something new
Nice post>
I would like to learn more about it. can you provide me some guidance. Please....?
Great post. Looking forward to learning more on the subject.
@Deepak The best way to learn more about each of these is to visit the links I provided for them. You really don't even have to read the full object_daddy page, just scroll down to the very end where he talks about object_daddy, this part is rather short. Or, download/install the plugin and read the README file, that will get you going. As for Shoulda, their page is a great simple tutorial, so surf over to that link and check it out. You'll be up and running in no time.
I was understanding only about 50% of all that. I wonder if people just absorb all that info by immersing themselves in it, or if it come only through study...
Chris,
Thanks for the great writeup and the feedback on object_daddy. We're moving our projects to github shortly so maybe we can more readily exchange patches.
It's good to see someone using the plugin and really kicking the tires. We use it all the time in our work but we don't stress it as much as when other people use it.
I'll ping you back when we get set up on github.
Thanks!
Rick
Rick, definitely let me know when you go to GitHub. I will then do a fork from your project there so they can be connected, etc. Did you look at the tiny (yet significant) change I made? I wonder if either a) you guys had it this way originally and then needed to change it for some reason, due to the way the docs are written (because as written the docs don't work/aren't right), or b) if it is just an oversight in the docs, or??? Anyway, it'll be great to have it all on GitHub so we can push/pull as needed. Thanks!
Chris,
Yossef here (another OGC/flogic guy, the 'ymendel' you see on b.logi.cx).
After some futzing with SSH keys, the github move has been effected. I'm going to look into your comments (here and b.logi.cx) and see what changes need to be made.
http://b.logi.cx/2008/4/15/gitting-on-the-bandwagon
http://github.com/flogic
@Yossef, thanks! As you may have seen, I killed my git version, then forked yours, made my change, then sent you guys a pull request. I understand if the change is not what you may want, but figured I'd send this to see how it all works, and since you mentioned you wanted to look at it.
Congrats on being a blog of note! :) tina
Nice... Keep on doing like hat. Check my place... Kiss to you all...
Thank you very much Chris.
Very helpful posts.
:)
Good site...like the contents...and the presentation.
http://nullstress.blogspot.com
As I primary do development in .NET, this was a very interesting read. Opuno
hey nice post,it is interested again,and helpful,share this information to this site http://filestance.com/
Chris,
I thought you might be interested in new developments and a new blog post about Object Daddy: http://b.logi.cx/2008/5/13/daddy-hasn-t-got-a-brand-new-bag
Thanks for the OD love, man (or at least the use).
ymendel thanks for the tip. I'm still making use of it, and with the enhancements/changes you guys made, I'm now simply using your GitHub version, and have killed my fork of it. Thanks!
Shoulda rocks! +1
I'll take Object Daddy for a test drive some day... seems a bit complex. I really like the factories-and-workers plugin:
http://code.google.com/p/factories-and-workers/
it uses slick metaprogramming to make create_{model} and valid_{model}_attributes methods
you can also just build, if you want to make an object without saving it. Seems to be more semantically rail-ish than "generate"
Hey David, do try object_daddy, it's really not complex, in fact, I think you'll find it makes things a bit more clear.
That said, it's funny, but since writing this post, I have actually been doing all my new development with RSpec. I will have to write another post on what convinced me to switch over. But, regardless, to me, there are actually only two testing framework choices: Should (which implies Test::Unit of course) and RSpec. Or, in other words, Shoulda should simply be a standard thing for Test::Unit based testing.
PS I forked factories-and-workers to github:
http://github.com/dfl/factories-and-workers/tree/master
Seems like the functionality is very similar to Object Daddy, but I prefer the syntax of Factories.
Post a Comment