How to encapsulate persistence in a Rails app?

324 views
Skip to first unread message

Gabriel Malkas

unread,
Feb 26, 2012, 3:44:36 PM2/26/12
to Objects on Rails
Hi everyone,

In chapter 12, Avdi explains why coupling models to Active Record is
not the best thing to do. I've been thinking about a way to decouple
persistence from my business objects, but I'm not happy with my ideas
so far.

First, I read the article by Piotr Solinica (http://solnic.eu/
2011/08/01/making-activerecord-models-thin.html), and although his
solution is a step in the right direction, I wonder if the business
object should know about persistence.

The main issue is that it makes it difficult to change Active Record
for Data Mapper later on for example, since the persistence class
reference is hard-coded in the business object's class.

I thought about using Dependency Injection, but it feels clunky.

A "persistence wrapper" could be another way, and it would really make
the business object unaware of persistence, pretty much like Exhibit
does for views, but I don't really want to do :

PersistentUser.new(User.new)

all over my Rails app.

Finally, I thought about using a Factory class. But then the question
is, where should I use it ? Using it in the models directly would make
them aware of persistence, which I don't think is a good idea...but
maybe this is not a real issue?

I am working on a school project and I'm still not sure whether I want
to use ActiveRecord or DataMapper, hence these questions. Besides, I
was unable to make NullDB work with Rails 3.2, so I'm really trying to
get rid of the need to think about persistence right now.

Do you have any thoughts about this?

Thanks,
Gabriel.

François Beausoleil

unread,
Feb 26, 2012, 5:02:12 PM2/26/12
to objects-...@googlegroups.com
I believe what you describe as a factory is the Repository pattern, from Patterns of Enterprise Application Architecture (PoEAA): http://www.martinfowler.com/eaaCatalog/repository.html

There are other persistence patterns in PoEAA as well. Worth a look at least.

François Beausoleil
http://blog.teksol.info/

Message has been deleted

Krasimir Angelov

unread,
Feb 27, 2012, 7:11:25 AM2/27/12
to objects-...@googlegroups.com
Hi,

Here's an example trying to solve that using the Repository pattern, but it only supports Riak as data store for now.

BTW, I'm also having troubles to setup NullDb with AR 3.2 (w/o loading all other Rails stuff).

Regards,
K.

Alexandre de Oliveira

unread,
Feb 27, 2012, 9:02:39 AM2/27/12
to objects-...@googlegroups.com
I'm kinda annoyed with AR lately. As a matter of exercise, I created a gem where I monkey patched Object#method_added, so:

class Person; end
class PersonMapper < DataMapper::Mapper; attributes :name, :age; end

whenever Person has a change in its variables (name, age), the PersonMapper will automatically know it (thought DataMapper::Mapper#unit_of_work and intelligently persist the domain object.


It's just food for thought, actually. I not sure that monkey patching Object#method_added would be a good thing in practice.

--Alexandre

Adam Guyot

unread,
Feb 27, 2012, 2:07:38 PM2/27/12
to objects-...@googlegroups.com
If no one has posted this yet: https://github.com/raganwald/homoiconic/blob/master/2011/11/COMEFROM.md
Seems somewhat relevant to this conversation.


A

Avdi Grimm

unread,
Feb 27, 2012, 2:28:07 PM2/27/12
to objects-...@googlegroups.com
Nice, I'd missed that one.

--
Avdi Grimm
http://avdi.org

I only check email twice a day. to reach me sooner, go to
http://awayfind.com/avdi

Stuart Fraser

unread,
Mar 9, 2012, 1:23:48 AM3/9/12
to objects-...@googlegroups.com
I got nulldb working after someone else's comment on this forum

# Gemfile
gem 'activerecord-nulldb-adapter',
      :git => "git://github.com/nulldb/nulldb.git"

#spec_helper_lite.rb
require 'nulldb'
require 'active_support/all'
require 'active_record'

module SpecHelpers

  def setup_nulldb
    schema_path = File.expand_path('../db/schema.rb', File.dirname(__FILE__))
    ActiveRecord::Base.establish_connection(:adapter => :nulldb, :schema => schema_path)
  end

  def teardown_nulldb
    ActiveRecord::Base.establish_connection(:adapter => :nulldb)
  end
end


For some reason I had to add active_support and active_record to make it stop complaining

It works for me quite nicely even though there is probably a more efficient way of doing it

elVanja

unread,
Mar 11, 2012, 6:09:22 AM3/11/12
to Objects on Rails
Apologies upfront for a lengthy post, but I've been trying to come up
with a solution to this issue Gabriel started (thanks man!), and I'm
kind of stuck.
Here is a "short" definition of the problem and a few directions my
thoughts took me.
Any help appreciated :-)

#Persistence / Business rules separation

##Goals

* business objects should not know about persistence
* persistence should be plugged into the business rules
* allowing for different implementations
* this should be configurable
* persistence should be configurable
* e.g. configure cache-able data vs direct database access

##Proposed solutions

###Abstract persistence layer returns hashes

*business object*

class ConferenceRoom
include PersistenceConversion #implements new_from_hash and
to_hash

def self.find_by_room_number(number)
new_from_hash
ConferenceRoomPersistence.find_by_room_number(number)
end

def book_me(from, to)
@from, @to = from, to
ConferenceRoomPersistence.update(to_hash)
end
end

*abstract persistence*

class ConferenceRoomPersistence
def self.find_by_room_number(number)
#just a placeholder
end

def self.update(hash)
#just a placeholder
end
end

*concrete persistence implementation*

ConferenceRoomPersistence.clas_eval do
def self.find_by_room_number(number)
#the real implementation
end

def self.update(hash)
#the real implementation
end
end

**Pros:**

* business objects expose only persistence methods that are need for
the rest of the application (see `ConferenceRoom.find_by_room_number`)
* from business objects to persistence there is only one route
* persistence layer is only in one place, easily replaceable

**Cons:**

* business objects directly reference the persistence layer, no
dependency injection (see `ConferenceRoom.book_me`)
* persistence is not implemented but it is still used in business
objects (see `ConferenceRoom.book_me`)

###Persistence layer appended to business objects

*business object*

class ConferenceRoom
def book_me(from, to)
@from, @to = from, to
end

def self.book(room_number, from, to)
room = find_by_room_number(room_number)
room.book_me(from, to)
end
end

*concrete persistence implementation*

ConferenceRoom.clas_eval do
include PersistenceConversion #implements new_from_hash and
to_hash

def self.find_by_room_number(number)
new_from_hash ...
end

def update
...
end

def book_me(from, to)
super
update
end
end

**Pros:**

* persistence implementation is glued to the domain object
* business objects get all the persistence methods "for free"
* from business objects to persistence there is only one route
* persistence layer is only in one place, easily replaceable

**Cons:**

* persistence methods used in other parts of the application are
"somewhere else"
* business objects sometimes do have to use persistence methods (see
`ConferenceRoom.book`), seams like a code smell
On Feb 27, 8:28 pm, Avdi Grimm <a...@avdi.org> wrote:
> Nice, I'd missed that one.
>
>
>
>
>
>
>
>
>
> On Mon, Feb 27, 2012 at 2:07 PM, Adam Guyot <shaoline...@gmail.com> wrote:
> > If no one has posted this yet:
> >https://github.com/raganwald/homoiconic/blob/master/2011/11/COMEFROM.md
> > Seems somewhat relevant to this conversation.
>
> > A
>
> > On Mon, Feb 27, 2012 at 9:02 AM, Alexandre de Oliveira
> > <chavedomu...@gmail.com> wrote:
>
> >> I'm kinda annoyed with AR lately. As a matter of exercise, I created a gem
> >> where I monkey patched Object#method_added, so:
>
> >> class Person; end
> >> class PersonMapper < DataMapper::Mapper; attributes :name, :age; end
>
> >> whenever Person has a change in its variables (name, age), the
> >> PersonMapper will automatically know it (thought
> >> DataMapper::Mapper#unit_of_work and intelligently persist the domain object.
>
> >> The code so
> >> far: https://github.com/kurko/datamapper2/blob/master/lib/dm2/unit_of_work...
>
> >> It's just food for thought, actually. I not sure that monkey patching
> >> Object#method_added would be a good thing in practice.
>
> >> --Alexandre
>
> --
> Avdi Grimmhttp://avdi.org

Alexander Paramonov

unread,
Apr 22, 2012, 3:30:15 AM4/22/12
to Objects on Rails
Reply all
Reply to author
Forward
0 new messages