Over the past few months, I've written many adapters for DataMapper, interfacing with a wide variety of data stores. A ReST-flavored one, TokyoTyrant, and some internal ones that I can't share. It's my second or third iteration on each, and I've been changing the Adapter API in dm-core as a result of my experiences. However, they're still not as polished or featureful as I'd like, due to still more deficiencies in the API. The most significant of these is the current types implementation.
Currently, its extremely difficult to inspect the type of a property. Consider the following code:
require 'dm-core'
DataMapper.setup(:default,
:adapter => :in_memory)
class Person
include DataMapper::Resource
property :id, Serial
property :name, String
property :created_at, DateTime
end
Person.properties.each do |p|
case p.type
when String then puts p.inspect
end
end
# no output
This is because property.type === String returns false, because its not really a String, is a DM::Types::String. We could add #===, but that would require everyone implementing a custom type to do the same.
The "native" dm types currently do no serialization at all. In most of my adapters, I serialize Time-like properties as iso8601 datetimes. I like it because it is concise, and human-readable. Currently, though, dm-core doesn't do any typecasting at all on native types. So when I save a DateTime to the data store, then load it again, I get it back as a string, not a DateTime. My preferred way would be to use a case statement, to detect if I'm loading a DateTime, and parse it appropriately, but that obviously doesn't work. It's also far from ideal.
Currently, anything used as a type has to to written as a DM::Type subclass. You also need to set a primitive, and methods for serializing/deserialize the value into something understandable by SQL. This is less than ideal for writing new types, and also completely unworkable for non-SQL adapter.
I have started a branch to address some of these issues. Some of the important features:
There's no such thing as a datamapper Types. The type attribute of a property is just a normal ruby class, and can be absolutely anything. This makes ruby-native adapters, such as in-mem and yaml, trivially easy to do, because no typecasting need be done. Writing new "types" is also easy, you just make a class.
The "custom" type is just a regular type, that sets some default options on the Property when its used. An example of the Serial type:
class Serial < Integer
include DataMapper::Type
default_options[:key] = true
default_options[:serial] = true
end
Just include the DataMapper::Type to get the default_options method, and set
the Property options you want. When you do property :foo, Serial, those
options will be automatically set on the property.
Types are just the ruby class, which means that case statements and === work
just like normal.
De/Serialization now become part of the responsibility of the adapters. I would like to have a mechanism where a type would be registered with the adapter. Some examples of how that might be accomplished:
class PostgresqlAdapter
add_save(UUID) { |val| val.to_s }
add_load(UUID) { |val| UUID.parse(val) }
end
This would allow custom, adapter-specific types to be implemented easily.
DataMapper, as it stands now, is completely unworkable for writing adapters that are non-trivial, and depart to far from the RDBMS mindset. Fixing and simplifying types will be a huge step in making it more plausible. My proposal is a pretty major departure from what we have now, but we get to greatly simplify some of the more complex code in DataMapper but doing this. Dan Kubb has is own ideas on how Types should be handled, but I'm not sure they're any better from the point-of-view of an adapter author. I hope to get some debate going over which is better, because I need better features in my adapters soon.