Create a gist now

Instantly share code, notes, and snippets.

Beware the circular Rails trap!

The Rails convention for associating dependent records has long been dependent on creating circular references between the master and detail record. Following a typical blog-type app, one might see

class Blog < ActiveRecord::Base
  has_many :posts
  # ...
end

and

class Post < ActiveRecord::Base
  belongs_to :blog
  # ...
end

Using this system, it's impossible to reason about the Blog or Post models independently of each other because any valid Post instance references a Blog which references one or more Post instances including the original. What to do?

One alternative is to adapt the Data Mapper Pattern on top of ActiveRecord, similarly to:

# post_manager.rb
class PostManager
  def find_all_in_blog(blog_id)
    PostRecord.where(blog_id: blog_id)
    # ... convert to PORO
  end

  private

  class PostRecord < ActiveRecord::Base
    self.table_name = 'posts'
    attr_accessible :blog_id
    # ...
  end
end

This can be used from the Blog class using something like the following:

# blog.rb

class Blog # Look, Ma, no inheritance required here!
  # ...
  def initialize(*)
    # ...
    id = # ...
    load_all_entries
    # ...
  end
  # ...

  private

  # ...
  def load_all_entries
    post_manager = PostManager.new
    @entries ||= post_manager.find_all_in_blog(id)
  end
  # ...
end

Note that though PostManager has an ActiveRecord::Base model as an implementation detail, there's no explicit, ActiveRecord-enforced dependency between the PostManager::PostRecord class and any ActiveRecord model for the Blog, or vice versa. The relationship is enforced and managed through the public interface of PostManager, which serves as an isolation port in the "ports-and-adapters" or "Hexagonal Rails" application architectures. Better adherence to SOLID principles make the classes easier to understand, debug and maintain.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment