views:

323

answers:

5

After working with ASP.Net MVC, it has me thinking about Rails. I worked with Rails prior, but am a little rusty. ASP.Net MVC tutorials recomment hiding data layer implementation with the repository pattern. This allows easiesr Dependency Injection for Unit Testing, and nice decoupling of the controller from the model implementation.

I remember Rails’ controllers using Active Record objects directly, and unit tests using test databases that could be setup and torn down with ease. That solves the need to swap out for unit testing, but still it seems like a bad idea to have so much ActiveRecord code exposed in the controller.

So my question is, what is the latest best practice here? Are real (not mocked) databases still used for unit testing? Do Rails developers call ActiveRecord directly, or an abstraction?

+3  A: 

My experience has been that Ruby on Rails integrates ActiveRecord so tightly (in most cases, it can become nearly completely transparent) that developers often use it without any Abstraction.

The thing to remember is that the Repository pattern and the Active Record pattern were both suggested in Patterns of Enterprise Architecture by Martin Fowler (which, if you haven't read it yet...you should). Active Record is tightly integrated in Rails. Microsoft .NET doens't tie you to a pattern...so the Repository pattern was adopted by the majority of the developers.

Justin Niessner
A: 

Folllowing Rails conventions always leads down the Path of Least Painful Memories, so it is advised.

Depending on your definition of "real"... For unit testing I've seen that people tend to use the same data schema as their main site, and seed the database before the tests are ran / during the tests (by using Factory Girl, Machinist or plain ol' fixtures) and then the tests are ran based on that data.

Rails developers call ActiveRecord directly on this data, as in the real world.

Ryan Bigg
+1  A: 

Controllers are supposed to access models in MVC. Rails is all about avoiding some of the needless abstractions that characterise the enterprise world.

John Topley
+3  A: 

Does ActiveRecord even really constitute the "data layer", I wonder? After all, its purpose is to abstract (to a fairly reasonable extent) the actual interaction storage. If I have a model that inherits from ActiveRecord::Base and I reference that model in a controller, am I really interacting with the data layer?

Looking at a brief description of the Repository Pattern I'd say that methods of the find_by_ are already giving you much of what it describes, which is good, isn't it? OK, the abstraction layer is leaky (one might more generously say "pragmatic") in that we can go a lot closer to the metal if need be, and find_by_sql for example will pretty much make it obvious that we're dealing with a relational database of some kind.

I'd recommend never (or maybe I should say "rarely and not without extreme justification" - it's always tricky using absolutes) putting code in controllers that makes it possible to infer the data platform being used. It should all be pushed into the models - named_scope can be very useful here. For complex results, consider using "presentation" objects as the interface (Struct and my personal favourite OpenStruct can be very useful here).

While ActiveRecord is the de facto standard, given that it installs with Rails, it's not the only game in town. For non-SQL databases, something different is necessary, but even in the SQL domain there's Datamapper (is that based on the eponymous PoEAA pattern?)

In Rails 3.0 it's going to be a lot easier to pick and choose components such as the ORM as Yehuda and the boys unpick and clean up the interfaces.

Mike Woodhouse
+1  A: 

You can do it either way. Most often, Rails functional tests are written to go all the way to the database, where data is populated from fixtures, as you describe.

But it's not uncommon to mock out the service layer calls, for example:

User.expects(:find_by_id).with("1").returns(u);
get :show, :id=>"1"

... or something like that. In fact, I do this all the time have control of the model object (or mock that out as well).

ndp