Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Provide mappers for some stdlib classes #11

Open
jgaskins opened this issue Nov 30, 2012 · 7 comments
Open

Provide mappers for some stdlib classes #11

jgaskins opened this issue Nov 30, 2012 · 7 comments

Comments

@jgaskins
Copy link
Owner

We should include mappers for some common core and standard-library classes so they can be persisted properly (i.e. without being Marshal.dumped). For example, an Article might have a list of tags, but they're in a Set rather than an Array.

Classes that should get mappers (will update if I come up with more):

  • Set
  • Bignum / BigDecimal
  • URI
  • Complex (maybe?)
  • Range

This could be a little tricky since mappers need to have intimate knowledge of mapped objects and some of these classes may be implemented differently on various Ruby implementations. In the case of the Set class, the data is kept in an internal hash called @hash in both MRI and Rubinius, but this isn't guaranteed to be true across other Ruby implementations that we might want to support.

Additionally, Set (and, presumably, other standard-library classes) doesn't provide direct access to that ivar, so we'll need to update serialization and deserialization to reach in via instance_variable_{set,get} if there is no attr_accessor. The good news is that this will remove the need for users to put attr_accessors on all their classes. I think it's okay for mappers to bypass an object's public API to have access to the state of an object since its sole purpose is to persist that state.

@jgaskins
Copy link
Owner Author

Another idea for serialization of these types of objects would be to provide a DSL in the mapper to specify custom serializer classes.

require 'set'

module Perpetuity
  module Serializers
    class SetSerializer
      def serialize(set)
        # Convert set into a Hash
      end

      def unserialize(serialized_set)
        # Convert serialized_set into a Set
      end
    end
  end

  module Mappers
    class SetMapper < Mapper
      map Set
      serialize_with Serializers::SetSerializer
    end
  end
end

This way, when the SetMapper object is told to serialize a set, it forwards it to an instance of the serializer class it's been configured with (defaulting to Perpetuity::Serializer if none specified).

@jgaskins
Copy link
Owner Author

One important thing to keep in mind is that collection objects should be queryable if at all possible. For example, if I wanted to see whether an Article has a specific tag, I should be able to says something like

Perpetuity[Article].select { tags.include? 'waffles' }

I haven't figured out a way to map objects like Set and keep them queryable.

@jcostello
Copy link

I just tried query a collection inside other class.

something like:

Perpetuity[User].select { |user| user.friends.any? }

When will be this feature available?

@jgaskins
Copy link
Owner Author

That's an interesting feature that I hadn't thought of. I'm going through how to model that in Postgres and it looks totally doable. We should be able to implement any?, none?, one? and count on those query attributes.

I'm not sure about the MongoDB adapter, though. We may only be able to do any? or none? there.

Also, since this doesn't pertain to persisting stdlib classes as attributes, I've opened a new issue #32 for discussion on this feature.

@acook
Copy link

acook commented Jan 20, 2014

I haven't tried this practically, but while we can't rely on the internal implementation of classes like Set, we can probably assume its external API won't radically change. With that in mind, we may be able to gleen enough information about the Set from #to_a and other methods to reversibly serialize it.

@jgaskins
Copy link
Owner Author

For Set, the best course of action here is probably to serialize as an array and require the user to declare the type to be a Set:

Perpetuity.generate_mapper_for Article do
  # ...
  attribute :tags, type: Set
end

When serializing, we would call to_a on it and when deserializing, we'd call to_set on the array we get back from the DB. I'm not sure how this will work yet because serialization is done entirely in the adapter.

That wasn't the case in the past but when separating the adapters from the core gem it was easier to just yank out all serialization. I think some of that will need to be pulled back into the mapper so there's a bit more control over it. This means that there'll need to be a little bit of restructuring and a better definition of layers between the objects.

I have a feeling that #15 will need to be implemented first and that there will need to be an intermediate object between the mapper and the data source that does the translations for both.

@acook
Copy link

acook commented Jan 23, 2014

Hmm, can't you just tag that value as a somewhere else? Maybe a tags_type column, for instance? If so, then you know how to serialize and deserialize it.

Alternatively, you could explicitly define the expected types on each end like:

  attribute :tags, serialize_to: Array, deserialize_as: Set

I'm assuming Postgres here, which has a built in Array type, but even so it could be serialized to an Array in MonogDB or even use the $addToSet operator.

I think the single type is also interesting, but without putting it in explicitly we could still derive that from the type thats passed in, but you're right it still needs to be stored somewhere. Perhaps its better to store it in the attribute option hash, or perhaps its better to store it with the data.

With a naive serialization to say YAML, it would store the data (as a string) in such a way as to return it to its original state, but it would be less efficient and would not leverage the power of the datastore.

This is more of a brain dump than any sort of final conclusion.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants