Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Ruby JavaScript
branch: master

Fetching latest commit…

Cannot retrieve the latest commit at this time

Failed to load latest commit information.
app
config
db
doc
lib/tasks
public
script
test
vendor/plugins
.gitignore
.rvmrc
Gemfile
Gemfile.lock
README
Rakefile
config.ru

README

== Welcome to Relationships

To get started:

  rake explain:populate   # creates some objects
  rake explain:go         # prints out some stuff

This app was written in response to http://modernsage.wordpress.com/2011/02/05/rails-3-0-arel-acrobatics/
Blog post at: http://ryanangilly.com/post/3207048558/stinky-magic

In that blog post, my buddy Chris is doing some stuff with Arel/AR that, in my opinion, is backwards.
It's causing him to extend ActiveRecord::Base in a way that I feel is unnecessary, and potentially
dangerous down the road

Chris wants to do stuff like:

  Forum.mine.posts.comments

The intent is that he wants to access all the comments owned by a post owned by a forum owned by a
particular user.  Working off the Forum model, he wants to scope things through the 'mine' scope,
which scopes things to an authenticated user.

There are two problems with this approach:

First, Forum.mine is a backwards way of accessing records belonging to an authenticated user.  Normally,
a Rails app would authenticate a user and provide helpers at the view and controller level
to access that user.  You'll see this in a lot of apps as:

  current_user

Now, the problem with Forums.mine is that in order for it to work, you have to have code
inside the model that goes back out to the authentication system.  Authentication is the controller's
domain.  It's an awkward coupling of concerns that causes your code to get all tangled up.

A much more "drop through" way to retrieve objects is to have the user be at the root when
going left to right.  The common way to get Forums owned by a person at the controller level would be:

  current_user.forums

The second problem is that:

  Forum.mine.posts.comments

Doesn't make sense.

Posts don't have comments.  A _single_ Post has comments.  The concept of Comments that belong to
many Posts doesn't exist here.  It's a confusion I've seen in new and experienced ActiveRecord developers
alike.

What is more straightforward would be this kind of accessor:

  current_user.forum_comments

I've implemented this in this app as a series of relationships, and an accessor that does a map:

  class User < ActiveRecord::Base
    has_many :forums
    has_many :posts, :through => :forums
  
    def forum_comments
      Comment.where(:post_id => posts.map(&:id))
    end
  
  end

Yes, this does two queries, but that's just fine.

Chris implemented an extension of ActiveRecord::Base that allows this to work:

  Forum.mine.posts.comments

And although I don't have any examples, I'm pretty this will break down as more complication
relationships are introduced.

The last paragraph of Chris' post says:

  You can use it with a single association or with a monster hierarchy of the form
  A -> B -> C -> D -> E -> F ->DRY, recursive, scalable. Now that’s what I call magic.

In my opinion, this is horribly dangerous.  When A, B, C, D, and E grow to be a couple million rows
(which is not very uncommon at all nowadays) you're gonna have a very non-scalable join to deal with.

While the solution I've proposed may not look as magical, I feel that it's correct to err on the side
of verbosity and explicitness when defining relationships in this way.

As a final note, if you really need to do A -> B -> C -> D -> E -> F a lot, that's a sign that
some denormalization may be justified.  Assuming that -> denotes has_many, F would have an e_id
on it.  In this case, adding an a_id would allow you to go A -> F directly and much faster.
Something went wrong with that request. Please try again.