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

Make mappers friendlier to Structs, etc #41

Open jgaskins opened 10 years ago

jgaskins commented 10 years ago

We assume that object state is stored in instance variables, so we don't go in via accessor methods — using accessor methods might modify state, trigger API calls, etc. Using instance variables means that your mappers have intimate knowledge of your models, but your models can remain completely oblivious to the mapper.

However, sometimes when you want to hammer out a quick proof of concept, you use a Struct instead of defining a class. Structs don't store their state into instance variables:

2.0.0p247 :004 > Boy = Struct.new(:name)
2.0.0p247 :007 > Boy.new('Sue').instance_variables
 => []

I'm not sure I want to make Structs a special case, but I'm open to ideas on how to use accessor methods instead of ivars.

acook commented 10 years ago

I can see the advantage of going in, expecting values to be stored in instance variables, but there's plenty of cases where that wouldn't be enough, Struct just being one example.

Perhaps something like this for both methods and generic operations:

Perpetuity.generate_mapper_for Article do
  attribute :title, accessor: :title
  attribute :body,  via: lambda{|article| article.content(:unescaped) }
end

I'd see the individual methods implemented before the lambda.

jgaskins commented 10 years ago

Yep. It'd be pretty nice to have other options to store/retrieve attributes. I like the ivars as the default (since I'd bet that's the most common). I originally thought about passing blocks instead of lambdas:

Perpetuity.generate_mapper_for Article do
  attribute :title do |article|
    article.title
  end
  # ... or ...
  attribute(:title) { |article| article.title }
end

Aesthetically, I'm not sure I like either one of these, though. Using do/end on separate lines breaks up the declarations. Using parentheses to override precedence for the curly-brace block would make it look different from other lines.

Also, it wouldn't account for storing the attributes. Something like the :accessor attribute would take care of that, but if we pass in procs, blocks, or lambdas (oh my) we'll need to allow two different ones to allow for deserialization as well as serialization.

Perpetuity.generate_mapper_for Article do
  attribute :body, reader: proc { |article| article.title }, writer: proc { |article, value| article.title = value }
end

The upside of something like this is that we can get rid of the hardcoded instance variable access and supply default procs instead (which technically would still hardcode the instance variable access, but at least it'd be overridable :-)).

jgaskins commented 10 years ago

Hmm … It seems some of the things I mentioned here might conflict with what we've discussed in #15. Maybe using procs might not be a good idea and we should stick to letting users specify whether to use ivars or accessor methods.