Question

I'm currently developing a large custom content management sollution in rails to handle many different content types (models) and their relations.

The whole data model is build upon active record and has features like content importing and exporting plus synchronization with other services (for example mobile sync to push content changes to smartphones).

For these tasks I have many data conversations, meaning on the one side the active record models and on the other side many different and already existing target formats.

  • JSON REST service to reflect changes in the model layer
  • rss feed to publish new content
  • import/export to proprietarian xml formats
  • e.t.c

For new data formats I can define the structure on my own, what in most cases means, let rails handle it by using the neat marshalling feature

    format.html do
      render 'show'
    end
    format.xml do
      render xml: { content:@content }
    end
    format.json do
      render json: { content:@content }
    end

But in the case when an existing data schema has to be served, several conversations have to be done:

Renaming keys : In the model, every object is identified by an id property but in the target format the objects property is names uid or OBJECT-ID...

Inlining related Objects : Given I have a model called Person that is related with an Address model. When using the Rails xml serialization the Address Object will be omitted or inlined under an tag. In a given target Format the address might have to be inline in the Person object, meaning the following output would be nedded

<person>
   <name>Ben</name>
   <street>Some Street</street>
   <city>Berlin</city>
</person>

Value transformation: a date property might be needed as a unix timestamp instead of a utc string

The naive sollution:

All this transformations could be done by hand whenever they are needed, meaning just put some ruby code that creates the target data structure:

data = {}
Person.all.each do |p|
    # rename property
    data[:guid] = p.id
    data[:name] = p.full_name
    # inline relation
    data[:street] = p.primary_address.street    
    data[:city] = p.primary_address.locality
    data[:member_since] = p.created_at.format(...)
end
render xml: { persons:data}

Or for xml only transformations builder templates could be used.

While this option is feasible and also flexible, it spreads the conversaion logic through the whole application and makes controllers grow and in a large application this will be bad for maintainability...

What I'm looking for is a schema based transformation for my models. Meaning I somewhere define a mapping from my activerecord model to the target schema (with a ruby dsl, in xml...) and just have to execute a schema conversation whenever I need a certain data format:

data = Article.all
# the parameter is the name of the target schema
converter = ModelConversation.new(:legacy_contact_list)
render xml: { contacts: converter.execute(data) }

So what I'm actually looking for is something similar to xslt but also applicable for json output and powered by ruby.

Any help/ideas or stories how you do your data conversations in rails would be appreciated.

Was it helpful?

Solution

I've been writing XSLT transformations for a few years, and I can only advise against XSLT or "something similar".

Since you have a Ruby application, just use Ruby! I think it already fits your needs.

Regarding your concern:

While this option is feasible and also flexible, it spreads the conversaion logic through the whole application and makes controllers grow and in a large application this will be bad for maintainability...

This would be something under your control. Just treat your converters as any other part of your application and keep code quality high. Your controllers won't grow if you put the conversion logic into the model itself or move it to a library. Refactor your converters to keep them concise.

Looking at your "naive" example

    # rename property
    data[:guid] = p.id
    data[:name] = p.full_name
    # inline relation
    data[:street] = p.primary_address.street    
    data[:city] = p.primary_address.locality
    data[:member_since] = p.created_at.format(...)

This code basically says that in your target format, id is called guid, full_name is called name and so on. I doubt you can write this much shorter than in the code you've already given. So I don't see the need for another technology here.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top