assisrafael / indemma

Client side, ES5 observable and REST, extensible modular model.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

indemma

Indemma (mind picture → memory), client side, ES5 observable and REST, extensible modular data model.

Summary

  • We are tired of calling .attr on our models.
  • We are tired of using templates instead of data bindings.
  • We want a decent kind of polyfill to view models and rest clients until polymer is out of beta.
  • Requirements
    • ES5 Getters and Setters (Shim ships with component, IE 10+)
    • ES7 Observer (Shim ships with component, IE ?+)

Installation

$ component install indefinido/indemma

Usage

Basic Functionality (Query, Observable, Advisable)

Basic functionality: Just a copy of ActiveRecord Interface on javascript.

Model

  require('indemma');

  var person = model.call({
    resource: 'person',
    record: {
      after_initialize: []                                      // Callbacks

      // Default attributes
      avatar: "assets/images/layout/avatar_placeholder.png"
    }
  });

  // In the future will return a promise object
  // for now it doesn't, i know it's bad
  person.create({name: "Arthur Philip Dent"}, {name: "Ford Perfect"});

  // some time passed here

  person.find(1) //  {name: "Arthur Philip Dent", subscribe: ..., before: ..., after: ..., ... }

  person.every()   // [{name: "Arthur Philip Dent", subscribe: ..., ... }, {name: "Ford Perfect", subscribe: ..., ... }]

  // TODO active record interface like: person.where(attribute: value)

Record

  require('indemma');

  var person = model.call({resource: 'person'}),

  arthur = person({
    name     : function () { return this.firstname + " " + this.surname; },
    firstname: "Arthur Philip",
    surname  : "Dent",
    species  : "Humam"
  });

  // Subscribe to all changes
  arthur.subscribe(function (updates) {
    var i = updates.length,  update;
    while (i--) {
       update = updates[i];
      console.log(update.name, update.type, update.oldValue, '→', update.object[update.name]); // also this[update.name]
    }
  });

  // Subscribe to single property change
  arthur.subscribe('firstname', function ( update) {
    console.log( update.name,  update.type,  update.oldValue, '→',  update.object[ update.name]); // also this[ update.name]
  });

  // Advice function calls
  arthur.after('name', function () {
    console.log('excuted after arthur.name()');
  });

  arthur.firstname = "Arthur Philip Dent";
  delete arthur.name

Extensions

Associations

Basic active record like associations

  require('indemma');

  // Activate association support
  require('indemma/lib/record/associations'); // Working on to be require('indemma/association')
  model.associable();

  var person = model.call({
    resource: 'person',
    has_many: 'towels'              // Yes! Say no to camelcase!
  }),

  towel = model.call({
    resource: 'towel',
    belongs_to: 'person'
  })

  // Lets have our data

  arthur = person({
    name     : function () { return this.firstname + " " + this.surname; },
    firstname: "Arthur Philip",
    surname  : "Dent",
    species  : "Humam"
  });

  // Magic (⚡) and Science (☣) happening now
  microfiber_towel = arthur.towels.create({
    material        : 'microfiber',
    color           : 'orange',
    functions_amount: Infinity
  });


  arthur.towels[0] === microfiber_towel                 // true
  arthur.towels.length                                  // 1

  microfiber_towel.person_id                            // 1
  microfiber_towel.person = person({name: "Ford"})      // 1
  microfiber_towel.person_id                            // 2 (This may vary depending on storage)
  arthur.towels.length                                  // 0

TODO table with all available methods per association

Nested Attributes

  require('indemma');

  // Activate association support
  require('indemma/lib/record/associations'); // Working on to be require('indemma/association')
  model.associable();

  var person = model.call({
    resource: 'person',
    nest_attributes: 'towel'
  }),

  towel = model.call({
    resource: 'towel'
  }),

  arthur = person({
    name     : function () { return this.firstname + " " + this.surname; },
    firstname: "Arthur Philip",
    surname  : "Dent",
    species  : "Humam"
  });

  arthur.towel_attributes = { material: 'microfiber', functions: Infinity }
  arthur.towels[0]; // { material: 'microfiber', functions: Infinity } (Still in beta)

Restful

  require('indemma');

  // Activate restful support
  // Compatible with default rails json rendering e.g. render :json => @person
  // Depends on resourceful module
  require('indemma/lib/record/restful'); // Working on to be require('indemma/restful')

  var person = model.call({
    resource: 'person'
  }),

  arthur = person({
    name     : function () { return this.firstname + " " + this.surname; },
    firstname: "Arthur Philip",
    surname  : "Dent",
    species  : "Humam"
  });

  // ⚡ + ☣ again
  arthur.save(); // POST /people

  // ... some time passes
  arthur.species = "Homo Sapiens Sapiens";
  arthur.save(); // PUT /people/1

  // serialization
  arthur.json(); //  {name: "Arthur Philip Dent", firstname: "Arthur Philip", surname: "Dent", species: "Humam"}

Resourcefull

Inflection and resource modules together.

  require('indemma/lib/record/resource'); // Working on to be require('indemma/resourceable')

  var person = model.call({
    resource: {
      name      : 'person',
      param_name: 'guy',
      scope     : 'world'
      // singular: true         // Works as singular resource too =D
    }
  }),

  arthur = person({
    name   : "Arthur Philip",
    species: "Humam"
  });

  // Also sets the accept header to application/json

  arthur.save();   // POST /world/person?guy[name]=Arthur Philip&guy[species]=Human

Scopable

Active Record like scopes and some finders

  require('indemma/lib/record/scopable'); // Working on to be require('indemma/scopable)

  // Sintax is up to change yet
  var person = model.call({
    resource: 'person',
    $gender: String,
    $age: Number
    $towel_ids: Array

    // $towel: function () {}          // Runtime scope builders to come
  });

  // Also sets the accept header to application/json

  person.all();               // GET /people
  person.gender('m');         // GET /people?gender=m
  person.age(32);             // GET /people?age=32
  person.none.age(32);        // GET /people
  person.towel_ids(1, 2, 3);  // GET /people?towel_ids[]=1&towel_ids[]=2&towel_ids[]=3

Maid

TODO make documentation

Storage (Persistance, Queryable & Storeable)

TODO make documentation

Validations support

  require('indemma');

  // model definition
  var person = model.call({
    resource  : 'person',

    email     : 'Email',                // By default uses type validation
    age       :  Number,
    birthday  : {type: Number},

    // You can use builtin validators sintax
    validates_presence_of  : 'age',
    validates_remotely     : 'email',   // Remote validator will do POST /people/validate?person[email]=arthur@earth.com

    // Coming soon!
    // validates  : {name: {beginsWith: 'arthur', presence: true}} // Or you can use the object syntax

  });


  arthur = person({
    name: "Arthur Philip Dent",
  });


  arthur.email = "arthur@earth.com";// "arthur@earth.com"
  arthur.valid;                     // true

  arthur.email = "wrong@@";
  arthur.valid;                     // false

  // When calling valid attribute will trigger validations
  arthur.errors;                    // [ email: ["Email 'wrongg@@' is in an invalid format.", "..."]
  arthur.errors.messages.email;     // "Email 'wrongg@@' is in an invalid format."

  // When using remote validations use the returned deferred
  arthur.validate(function (record, validation_results...) {
    alert(record.errors.messages.email);
  });

  <div>
    <input class="invalid" data-value="user.email" data-class-invalid="user.errors.messages.email" />
  </div>

  // Validators reference
  person.validators                 // [ {attribute_name: 'email', type: 'Email', ... } , ... ]


  // Suport for custom validators coming soon!
  // person.validators.phone = function (record, attribute, value) {... }

Adapters

Rivets
 require('indemma');

  // Activate restful support
  // Requires observer-shim
  // Compatible with default rails json rendering e.g. render :json => @person
  require('indemma/lib/record/rivets'); // Working on to be require('indemma/adapters/rivets')

  var person = model.call({resource: 'person'}),

  arthur = person({
    name     : function () { return this.firstname + " " + this.surname; },
    firstname: "Arthur Philip",
    surname  : "Dent",
    species  : "Humam"
  }),

  template = '<div class="person">' +
             '  <span data-html="person.name"></span>    ' +
             '  <span data-html="person.species"></span> ' +
             '</div>';

  document.body.innerHTML = template;
  element = document.body.children[0];

  arthur.tie(element); // equivalent to rivets.bind(element, {person: arthur});

  arthur.species = "Homo Sapiens";

TODO

Move API to a custom page or the github wiki

Tests!

A medium part of tests has been written, but more on the go! You can help by implementing the body of some spec

Define property on steroids sintax support

With setter on steroids

  require('indemma');

  // model definition
  var person = model.call({
    resource : 'person',
    name     : {
      set : function (name) { this.name = name.split(' '); },
      get : function (name) { this.name.join(' '); }
    },
    firstname: {
      get : function () { this.name[0] },
    }
  });

  arthur = person({
    name: "Arthur Philip Dent",
    species  : "Humam"
  });

  arthur.firstname  // Arthur

Inflections support

  require('indemma');

  var person = model.call({resource: 'person'});
  person.plural     // 'people'

Storage extension

  require('indemma');

  model.storable()

  arthur = person({
    name: "Arthur Philip Dent",
    species  : "Humam",
  });

  arthur.save() // Store on selected storage (localStorage, memory, etc ...)

Single extensions call option

  require('indemma');

  model(['advisable', 'observable', 'restfulable', 'maid', ... ]);

Credits

Built upon the lovely coffeescript language Building with the prototypal paradigm library stampit

This project rocks and uses WTFP-LICENSE

About

Client side, ES5 observable and REST, extensible modular model.


Languages

Language:JavaScript 92.7%Language:CoffeeScript 3.6%Language:HTML 3.6%Language:Ruby 0.1%Language:Shell 0.1%Language:Makefile 0.1%