jgaskins / perpetuity

Persistence gem for Ruby objects using the Data Mapper pattern
http://jgaskins.org/blog/2012/04/20/data-mapper-vs-active-record/
MIT License
250 stars 14 forks source link

ActiveModel wrapper #3

Closed jgaskins closed 11 years ago

jgaskins commented 12 years ago

What do we need ActiveModel for?

Rails counts on a lot of ActiveModel methods in order to do things like form_for in views and redirect_to @foo in controllers.

How should it be done?

As always, several possibilities floating around in my head.

Using mappers as ActiveModel wrappers

Consider this gist. Unfortunately, this means that we need to make the mapper respond to all of the attributes (for form_for) and the id field (for redirect_to). This is trivial to do, but feels weird.

Include wrapper functionality into domain objects

Another possibility is to include the ActiveModel wrapper into the domain class (essentially just moving the include Perpetuity::ActiveModelWrapper call in the aforementioned gist). This would make the ActiveModel functionality expected inside those objects, but it means that the objects themselves aren't entirely decoupled from Rails. The primary idea behind Perpetuity is that there are no other dependencies other than those required for the domain objects to do their job.

Create an entirely different class specifically for wrapping domain objects to use ActiveModel

This idea maintains the purity of the domain objects, keeps the domain-object calls outside of the mappers and still allows us to use ActiveModel functionality. This seems like it might be the best solution, but I'm not 100% sold on it yet. Here's an example. If we could come up with a smoother way of calling it, it might be better.

jgaskins commented 11 years ago

This is currently not exactly necessary and will become less necessary for Rails 4.0.

In current Rails (3.2) apps, you can put the following in the model:

class Article
  if defined? ActiveModel
    extend ActiveModel::Naming
    include ActiveModel::Conversion
  end

  def persisted?
    defined? @id # Mappers inject the @id ivar when inserting into/retrieving from the DB
  end
end

This will give ActiveModel functionality to your models without worrying about it during unit tests (where ActiveModel isn't loaded). In Rails 4.0, you will simply need to include ActiveModel::Model inside the conditional. I think it's best to keep the ActiveModel interface within the models rather than trying to load some Perpetuity-specific mixin. Models should ideally have no idea about the persistence mechanisms used and so shouldn't even know Perpetuity exists.

Unfortunately, this gets kinda weird with the persisted? method since it has to know the criteria to determine whether or not it's been saved to the DB, but I think that's a failure of the API expected of models by Rails.