Advanced
Data Modelling
IN The Frontend

/whois




Jan-Oliver Pantel
Backend Developer @CouchCommerce
Twitter: @JanPantel



Data Modelling

In The Backend




  • ORMs
  • Rails, Symfony, Laravel are shipped with an ORM


  • even Microsoft MVC


Laravel Model Example

class User extends Eloquent {

    protected $fields = array('id', 'name', 'password');

    public function posts() {
        $this->hasMany('Post');
    }

}

class Post extends Eloquent {
    protected $fields = array('id', 'title', 'body');
    public function user() {
        $this->belongsTo('User');
    }
}
$posts = User::find(1)->posts()->get();
//SELECT p.* FROM user AS u WHERE u.id = 1 
//INNER JOIN post AS p ON p.user_id = u.id
$user = Post::find($post->id)->user()->get();
//SELECT u.* FROM post AS p WHERE p.id = 1
//INNER JOIN user AS u ON p.user_id = u.id

SQL and orms

  • Building SQL dynamically via an ORM increases testability of the code
  • Switching databases becomes easier due to access layer abstraction
  • Renaming a database table or column doesn't trigger a big refactoring of wild SQL statements within the code
  

What's About the frontend?








$http

 $http.get('http://www.mysite.com/user/1/posts')
    .success(function (response) {
        $scope.posts = response.data;
    });

Ok, we can live with things like that

but


$http.get(
   'http://www.mysite.com/posts/'
   + $scope.chosenPost.id
   + '/comments'
).success(function (response) {
    $scope.comments = response;
})


$resource

var User = $resource('/user/:userId');
var Post = $resource('/user/:userId/posts');
var Comment = $resource('/post/:postId/comments');

var user = User.get({ userId: 1 }, function () {
    var posts = Post.query({ userId: user.id }, function () {
        $scope.posts = posts;
    });
});

$scope.changePost = function (post) {
    var comments = Comment.query({
        postId: post.id
    }, function () {
        $scope.comments = comments;
    });
};




But







but

  • No promises
  • You can't use it in ui-router's resolve without workarounds

resolve: {
    user: function ($q, $resource) {
        var d = $q.defer();
        var User = $resource('/user/:id');
        var user = User.get({ id: 1 }, function () {
            d.resolve(user);
        });
        return d.promise;
    }
}
  • You have to define a $resource object for each request
  • No custom methods
  • No relations between entities

Restangular

Restangular.one('user', '1').get().then(function (user) {
   user.all('posts').getList().then(function (posts) {
      $scope.posts = posts;
   });
});

$scope.changePost = function (post) {
    Restangular.one('post', post.id).all('comments').getList()
        .then(function (comments) {
            $scope.comments = comments;
        });
};

Not bad at all

but

  • You have to restart the query if you don't subquery any more

var posts = Restangular.one('user', 1).all('posts').getList();
// => /user/1/comments

var comments = posts[0].all('comments').getList();
// => /user/1/posts/<id>/comments

Restangular.one('post', <id>).all('comments').getList();
// => /post/<id>/comments
  • The majority of REST APIs won't have such a deep entity query
  • Most times you get your comments via /user/1/posts
    and comments via /post/<id>/comments
  • Configuration only globally during app init
  • STRINGS :(

And now changing api endpoints

Rest Orm

Why?




The frontend Got his SQL called



REST

Models

orm.register('User', {
   url: 'user', //optional
   relations: {
       hasMany: {
           Post: {
               model: 'Post', //optional
               //will get pluralized automatically
           }
       },
       hasOne: {
           Girlfriend: {
               model: 'User'
               //will get singularized by default
           }
       }
   }
});

QuerIes

DataContext.User.find(1).posts.get().success(function (posts) {
    $scope.posts = posts;
});
// => /user/1/posts

$scope.changePost = function (post) {
    post.reset().comments.get().success(function (comments) {
        $scope.comments = comments;
    });
    // => /post/<id>/comments
};

If you call reset the query will be changed

But

Query States

DataContext.Post.find(1).User.get().success(function (user) {
    user.reset().girlfriend.get().success(function (girlfriend) {
        $scope.girlfriend = girlfriend;
    });    // => /user/1/girlfriend    user.posts.get().success(function (posts) {
        $scope.posts = posts;
    });    // => /user/1/posts
});
You don't want to reset your user? NP:
user.clone().reset().girlfriend.get().success(function (girlfriend) {
    $scope.girlfriend = girlfriend;
});
//OR SUGAR BABY
user.cloneReset().girlfriend....


CRUD

DataContext.User.find(1).remove();
//OR
DataContext.User.remove(1);
// => DELETE /user/1
DataContext.User.find(1).get().success(function (user) {
    user.name = "Jan";
    user.save();
    // => PUT /user/1
});
DataContext.User.create({ name: "Jan" }).success(function (user) {
    user.girlfriend.attach(gf);
    // => PUT /user/<user.id>/girlfriend/<gf.id>
});
// => POST /user

Working with Relations

DataContext.User.find(1).girlfriend.detach().success(function () {
    location.href = "www.youp***.com";
});
// => DELETE /user/1/girlfriend
DataContext.User.find(1).girlfriend.attach(gf).success(function (user) {
    console.log('Gratz bro ;)');
});
// => PUT /user/1/girlfriend/<gf.id>
DataContext.User.find(1).posts.remove(post).success(/*...*/);
// => DELETE /user/1/posts/<post.id>
 DataContext.User.find(1).posts.add(post).success(/*...*/);
// => PUT /user/1/posts/<post.id>

Interested?



Not everything done yet

Check: janpantel/angular-restorm

@github (development branch)

Also beta testers are welcome

RoadMAP


  • Configurable caching
  • Documentation

Interested In Contributing Code or Ideads?




Twitter: @JanPantel
Mail: jan.pantel@gmail.com

Thanks


Advanced Data ModellingIn The Frontend

By Jan Pantel

Advanced Data ModellingIn The Frontend

  • 370