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.