IFTTT / polo

Polo travels through your database and creates sample snapshots so you can work with real world data in development.
http://ifttt.github.io
MIT License
775 stars 48 forks source link

Add generic Translator and Reader base #32

Closed maddievision closed 8 years ago

maddievision commented 8 years ago

This adds the idea of having a configurable Translator (transforms AR instances into a serial form) and configurable Reader (transforms serial form back into AR instances).

SqlTranslator now inherits from a TranslatorBase base class.

A JsonTranslator has been also added, which outputs the AR instances into a JSON string with an array of objects of the following structure:

{
  "table": (table name of the class of AR instance),
  "attributes": {
    (attributes of the AR instance)
  }
}

A JsonReader (which inherits from ReaderBase) has been added which takes the above format and transforms it into AR instances. It is dependent on the model classes being loaded as it will iterate through ActiveRecord::Base descendants to create a mapping of table name to model classes. Since there is no straightforward way of implicitly preload the models (and that behavior may be unideal for certain projects) so I don't believe we should be doing that.

The configuration now accepts the options :use_reader and :use_translator (they have defaults to JsonReader and SqlTranslator, respectively).

A new convenience method Polo#read has added to read and translate a serialized form of instances.

Example

Polo.configure { use_translator Polo::JsonTranslator }
j = Polo::Traveler.explore(Chef, 2, :recipes)

will produce

[
  {
    "table":"chefs",
    "attributes": {
      "id": 2,
      "name": "Andrew"
    }
  },
  {
    "table":"recipes",
    "attributes":
    {
      "id": 3,
      "title": "Vegemite Sandwich",
      "num_steps": null,
      "chef_id": 2
    }
  },
  {
    "table": "recipes",
    "attributes": {
      "id": 4,
      "title":"Chicken Burger",
      "num_steps": null,
      "chef_id": 2
    }
  }
]

(whitespace added for legibility)

Polo.configure { use_translator Polo::SqlTranslator }
Polo.read(j)

will then produce:

INSERT INTO `chefs` (`id`, `name`) VALUES (2, 'Andrew')
INSERT INTO `recipes` (`id`, `title`, `num_steps`, `chef_id`) VALUES (3, 'Vegemite Sandwich', NULL, 1)
INSERT INTO `recipes` (`id`, `title`, `num_steps`, `chef_id`) VALUES (4, 'Chicken Burger', NULL, 1)

TODO

maddievision commented 8 years ago

I'm wondering if we should rename TranslatorBase to Translator, and come up with another name for the Translator. Same with ReaderBase and Reader

The Polo::Reader class as it is might be completely unnecessary.

nettofarah commented 8 years ago

wow.. this is looking cool, @djbouche. I like where this is headed.

I'll look into it in depth tomorrow to try and give you some accurate feedback.

nettofarah commented 8 years ago

Hey, @djbouche. I just thought of something that could simplify things a lot.

What if insted of using a Reader and Importer we just modified Polo.explore to take in an extra Translator object as an argument?

We could do something like this:

  def self.explore(base_class, id, dependencies={}, translator=Translator.new)
    traveler = Traveler.collect(base_class, id, dependencies)
    translator.run(traveler.selects)
  end

This might reduce the number of changes we need to make to the core library and allow for a lot of flexibility. i.e. you can pass in whatever custom translator you want (as long as it complies to the Polo::Translator protocol).