Giter VIP home page Giter VIP logo

ember-model's Introduction

Ember.Model Build Status

Introduction

Ember Model (EM) is a simple and lightweight model library for Ember. It intentionally supports a limited feature set. The main goal is to provide primitives on top of $.ajax that are required by Ember.

EM is still a work in progress, but it's flexible and stable enough to be used in production apps today. It was extracted out of an Ember app. Please see the issues section for a list of bugs and planned features.

Getting Started with Ember Model

Ember CLI

ember install ember-model

Bower

bower install ember-model --save

Getting started Embercast

Need more help getting started? Join us in #ember-model on Freenode.

Features

  • BYO$A (bring your own $.ajax)
  • Relationships (hasMany/belongsTo)
  • Focused on performance
  • Automatic coalescing of multiple findById calls into a single findMany
  • Fixtures
  • Identity map (per class)
  • Promises everywhere
  • Customizable RESTAdapter

If you want more features than Ember Model provides, file an issue. Feature requests/contributions are welcome but the goal is to keep things simple and fast.

Example usage

Ember CLI / ES6 modules

// app/models/user.js
import { Model, attr, hasMany } from 'ember-model';

var User = Model.extend({
  id: attr(),
  name: attr(),
  comments: hasMany("comment", {key: 'comment_ids'})
}).reopenClass({
  url: "/users"
});

export default User;
// app/models/comment.js
import { Model, attr, hasMany } from 'ember-model';

var Comment = Model.extend({
  id: attr(),
  text: attr()
}).reopenClass({
  url: "/comments"
});

export default Comment;
// create example
var newUser = this.store.createRecord('user', {name: "Erik"});
newUser.save(); // POST to /users

// hasMany example
var comments = newUser.get('comments');
comments.create({text: "hello!"});
comments.save(); // POST to /comments

// find & update example

this.store.find('user', 1).then(user => { // GET /users/1
  user.set('name', 'Kris');
  user.get('isDirty'); // => true
  user.save(); // PUT /users/1
});

Globals

var attr = Ember.attr, hasMany = Ember.hasMany;

// Model definitions
App.User = Ember.Model.extend({
  id: attr(),
  name: attr(),
  comments: hasMany("App.Comment", {key: 'comment_ids'})
});

App.User.url = "/users";
App.User.adapter = Ember.RESTAdapter.create();

App.Comment = Ember.Model.extend({
  id: attr(),
  text: attr()
});

App.Comment.url = "/comments";
App.Comment.adapter = Ember.RESTAdapter.create();

// create example

var newUser = App.User.create({name: "Erik"});
newUser.save(); // POST to /users

// hasMany example
var comments = newUser.get('comments');
comments.create({text: "hello!"});
comments.save(); // POST to /comments

// find & update example

var existingUser = App.User.find(1); // GET /users/1
existingUser.set('name', 'Kris');
existingUser.get('isDirty'); // => true
existingUser.save(); // PUT /users/1

Store API

Store#find(<type>) - find all records by type, returns a promise

Store#find(<type>, <id>) - find by primary key (multiple calls within a single run loop can coalesce to a findMany), returns a promise

Store#find(<type>, <object>) - find query - object gets passed directly to your adapter, returns a promise

Store#createRecord(<type>, <object>) - create a new record

Model API

Model.create - create a new record

Model#save - save or update record

Model#deleteRecord - delete a record

Model#load(<id>, <object>) - load JSON into the record (typically used inside adapter definition)

Model#toJSON - serialize the record to JSON

Model.find() - find all records

Model.find(<String|Number>) - find by primary key (multiple calls within a single run loop can coalesce to a findMany)

Model.find(<object>) - find query - object gets passed directly to your adapter

Model.fetch() - find all records, returns a promise

Model.fetch(<String|Number>) - find by primary key (multiple calls within a single run loop can coalesce to a findMany), returns a promise

Model.fetch(<object>) - find query - object gets passed directly to your adapter, returns a promise

Model.load(<array>) - load an array of model data (typically used when preloading / sideloading data)

Adapter API

Ember.Adapter = Ember.Object.extend({
  find: function(record, id) {}, // find a single record

  findAll: function(klass, records) {}, // find all records

  findMany: function(klass, records, ids) {}, // find many records by primary key (batch find)

  findQuery: function(klass, records, params) {}, // find records using a query

  createRecord: function(record) {}, // create a new record on the server

  saveRecord: function(record) {}, // save an existing record on the server

  deleteRecord: function(record) {} // delete a record on the server
});

Attribute types

Attributes by default have no type and are not typecast from the representation provided in the JSON format.

Built in attribute types

Ember Model has built in Date and Number types. The Date type will deserialize strings into a javascript Date object, and will serialize dates into ISO 8601 format. The Number type will cast into a numeric type on serialization and deserialization.

App.Post = Ember.Model.extend({
  date: attr(Date),
  comment_count: attr(Number)
});

Custom attribute types

To provide custom attribute serialization and deserialization, create an object that has serialize and deserialize functions, and pass it into the attr helper:

var Time = {
  serialize: function(time) {
    return time.hour + ":" + time.min;
  },
  deserialize: function(string) {
    var array = string.split(":");
    return {
      hour: parseInt(array[0], 10),
      min: parseInt(array[1], 10)
    };
  }
};

var Post = Ember.Model.extend({
  time: attr(Time)
});

Default values

Attributes can have a default value.

App.Post = Ember.Model.extend({
  tags: attr(Array,{defaultValue:[]})
});

Relationships

Ember Model provides two types of relationships hasMany and belongsTo. Both types of relationships can either be embedded or referenced by ids.

Defining Relationships

Relationships are defined by using relationship computed property macros in place of Ember.attr. There are two macros available, one for each type of relationship.

Ember.belongsTo(type, options) - Provides access to a single related object.

Ember.hasMany(type, options) - Provides access to an array of related objects.

Both relationships take two arguments.

  • type - Class of the related model or string representation (eg. App.Comment or 'App.Comment').

  • options - An object with two properties, key which is required and embedded which is optional and defaults to false.

    • key - indicates what property of the JSON backing the model will be accessed to access the relationship
    • embedded - If true the related objects are expected to be present in the data backing the model. If false only the primaryKeys are present in the data backing the model. These keys will be used to load the correct model.

Relationship Examples

// Embedded Relationship Example

postJson = {
  id: 99,
  title: 'Post Title',
  body: 'Post Body',
  comments: [
    {
      id: 1,
      body: 'comment body one',
    },
    {
      id: 2,
      body: 'comment body two'
    }
  ]
};

App.Post = Ember.Model.extend({
  id: Ember.attr(),
  title: Ember.attr(),
  body: Ember.attr(),
  comments: Ember.hasMany('App.Comment', {key: 'comments', embedded: true})
});

App.Comment = Ember.Model.extend({
  id: Ember.attr(),
  body: Ember.attr()
});
// ID-based Relationship Example

postJson = {
  id: 99,
  title: 'Post Title',
  body: 'Post Body',
  comment_ids: [1, 2]
};

commentsJson = [
  {
    id: 1,
    body: 'Comment body one',
    post_id: 99
  },
  {
    id: 2,
    body: 'Comment body two',
    post_id: 99
  }
];

App.Post = Ember.Model.extend({
  id: Ember.attr(),
  title: Ember.attr(),
  body: Ember.attr(),
  comments: Ember.hasMany('App.Comment', {key: 'comment_ids'})
});

App.Comment = Ember.Model.extend({
  id: Ember.attr(),
  body: Ember.attr(),
  post: Ember.belongsTo('App.Post', {key: 'post_id'})
})

Working with relationships

Working with a belongsTo relationship is just like working any other Ember.Model. An Ember.Model instance is returned when accessing a belongsTo relationship, so any Model methods can be used such as save() or reload().

comment.get('post').reload(); // Reloads the comments post

post.get('comments.lastObject').save(); // Saves the last comment associated to post

Accessing a hasMany relationship returns a HasManyArray or an EmbeddedHasManyArray which have useful methods for working with the collection of records. On any type of hasMany relationship you can call save() and all the dirty records in the collection will have their save() methods called. When working with an embedded hasMany relationship you can use the create(attrs) method to add a new record to the collection.

post.get('comments').save(); // Saves all dirty comments on post

// Below only works on embedded relationships
post.get('comments').create({body: 'New Comment Body'}); // Creates a new comment associated to post

Customizing

There are a few properties you can set on your class to customize how either Ember.Model or Ember.RESTAdapter work:

primaryKey

The property Ember Model uses for a per-record unique value (default: "id").

App.User = Ember.Model.extend({
  token: attr(),
  name: attr()
});
App.User.primaryKey = 'token';
GET /users/a4bc81f90
{"token": "a4bc81f90", "name": "Brian"}

rootKey

When RESTAdapter creates a record from data loaded from the server it will use the data from this property instead of the whole response body.

App.User = Ember.Model.extend({
  name: attr()
});
App.User.rootKey = 'user';
GET /users/1
{"user": {"id": 1, "name": "Brian"}}

collectionKey

When RESTAdapter creates multiple records from data loaded from the server it will use the data from this property instead of the whole response body.

App.User = Ember.Model.extend({
  name: attr()
});
App.User.collectionKey = 'users';
GET /users
{"users": [{"id": 1, "name": "Brian"}]}

camelizeKeys

If the server sends keys with underscores (ex: created_at), rather than camelized (ex: createdAt), setting this option to true makes Ember Model automatically camelize the keys.

App.User = Ember.Model.extend({
  firstName: attr()
});
App.User.camelizeKeys = true;
GET /users/1
{"id": 1, "first_name": "Brian"}
user.get('firstName') // => Brian

urlSuffix

By default no suffix is added to the url. You may want to specifically add one if using the same url for html and json requests.

App.User = Ember.Model.extend({
  first_name: attr()
});
App.User.urlSuffix = '.json';
GET /users/1.json
{"id": 1, "first_name": "Brian"}

Customize Ajax Settings

When using RESTAdapter custom headers and ajax settings can be applied by extending RESTAdapter and defining ajaxSettings

App.CustomAdapter = Ember.RESTAdapter.extend({
  ajaxSettings: function(url, method) {
    return {
      url: url,
      type: method,
      headers: {
        "authentication": "xxx-yyy"
      },
      dataType: "json"
    };
  }
});

or it can be done at create time of the RESTAdapter

App.User.adapter = Ember.RESTAdapter.create({
  ajaxSettings: function(url, method) {
    return {
      url: url,
      type: method,
      headers: {
        "authentication": "xxx-yyy"
      },
      dataType: "json"
    };
  }
});

Building Ember Model

Ember Model uses node.js and grunt as a build system, These two libraries will need to be installed before building.

To build Ember Model, clone the repository, and run npm install to install build dependencies and grunt to build the library.

Unminified and minified builds of Ember Model will be placed in the dist directory.

How to Run Unit Tests

Setup

Ember Model uses node.js and grunt as a build system and test runner, and bower for dependency management.

If you have not used any of these tools before, you will need to run npm install -g bower and npm install -g grunt-cli to be able to use them.

To test Ember Model run npm install to install build dependencies, bower install to install the runtime dependencies and grunt test to execute the test suite headlessly via phantomjs.

If you prefer to run tests in a browser, you may start a development server using grunt develop. Tests are available at http://localhost:8000/tests

Who's using Ember Model?

Are you using Ember Model? Submit a pull request to add your project to this list!

Special Thanks

Yehuda Katz (@wycats), Tom Dale (@tomdale), Igor Terzic (@igorT), and company for their amazing work on Ember Data. I believe it's the most ambitious JS project today. The goal is someday everyone's JSON APIs will be conventional enough that Ember Data will be the best choice of data library for Ember. Until then, Ember Model will make it easy to get up and running quickly with Ember.

Bitdeli Badge

ember-model's People

Contributors

ahacking avatar alexspeller avatar arturmalecki avatar chocoboba avatar ckung avatar connorhd avatar darthdeus avatar drogus avatar dwickern avatar ebryn avatar eventualbuddha avatar existentialism avatar gavinjoyce avatar guilhermeaiolfi avatar gunn avatar heartsentwined avatar jdjkelly avatar jnovatnack avatar josbeir avatar jpweeks avatar juggy avatar karlguillotte avatar kingpin2k avatar liukaren avatar narkeeso avatar patocallaghan avatar raytiley avatar satchmorun avatar spectras avatar trek avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

ember-model's Issues

FixtureAdapter#findMany returns a RecordArray full of nulls

Hi Erik,

Considering this Employee Model. I am using the REST Adapter to load an Employee from the server.

App.Employee = Ember.Model.extend({
  firstName: Ember.attr('string'),
  lastName: Ember.attr('string'),
  email: Ember.attr('string'),
  phoneNumbers: function() {
      return App.PhoneNumber.find('1323 1262'.w());
  }.property()
});

Everything is fine until I use phoneNumbers in my template with the each helper.

{{#each phoneNumber in phoneNumbers}}
  {{phoneNumber.id}}
{{/each}}

I am getting the error: You must pass at least an object and event name to Ember.addListener. I think, the reason is that at first phoneNumbers.length === 2 but both values are nulls.

Everything is working as expected, if I comment out Ember.RecordArray.length CP.

Thanks

Push support

Kinda related to #4. We want to be able to partially create models with data received from push notifications and have them trigger a find to retrieve their full content.

Force object reload after save()

Im saving a record to the server that has multiple joins. How can I force the record to reload from the server after the save or can I return the updated json and replace the records current state?

something along these lines

  question.save().then(function() {
    question.reload();
    question.set('isEditing', false);
  });

or

  question.save().then(function(data) {
    // ... update the state of the record
    question.set('isEditing', false);
  });

ember-model and its dependencies (grunt)

Hi,

I'm trying to use ember-model and grunt in windows 7 64bits. But npm install complains that I don't have python, installing python it complains that I don't have Visual Studio. WTF? I'm downloading it right now, but will I have to download 400+MB of things to just build ember-model in Windows?

The package that requires that is execSync, that requires fii, that requires node-gyp that blows everything.

Trouble running tests with new build process

Hi,

This is most likely a configuration issue on my side, but if I do a clean clone of ember-model I'm unable to run the tests. After npm install / bower install / grunt test I get the following output:

Running "qunit:cli" (qunit) task
Testing tests/index.html Fatal error: spawn EACCES

I read up that EACCES refers to Node not having the correct privileges to access the file system so as a temporary hack, I ran:

 chmod -R 777 *

I know this is not best practice but just wanted to remove possible permissions issue. I now run into the following issue but I'm not quite sure how to fix it:

Running "qunit:cli" (qunit) task
Testing tests/index.html 
Warning: PhantomJS timed out, possibly due to a missing QUnit start() call. Used -- force, continuing.
Warning: 0/0 assertions ran (0ms) Used --force, continuing.

I'm not sure if there is something funny with my system Node.js config or if its the test configuration.

I have re-installed npm and node to the latest version v0.10.2

Model#update

Would be nice to have a way to force a model to fetch updated data from the server

Add ability to rollback changes to a model object

Maybe I'm missing it, but is there a canonical way to cancel changes to a model object if the user navigates away without submitting?

Ember Data has the concept of transactions and rollback. Is there something similar in Ember Model?

RESTAdapter#find requires Record.rootKey but does not use it

  find: function(record, id) {
    var url = this.buildURL(record.constructor, id),
        rootKey = record.constructor.rootKey;

    if (!rootKey) {
      throw new Error('Ember.RESTAdapter requires a `rootKey` property to be specified');
    }

    return this.ajax(url).then(function(data) {
      Ember.run(record, record.load, id, data);
    });
  }

I expected to see the following, or something equivalent. (or maybe not requiring rootKey, but if present, using it in this manner)

Ember.run(record, record.load, id, data[rootKey]);

ember-model and new async route in ember RC6

Hi,

I'm not sure if it's an ember-model problem, sorry if it's not.
I updated to ember RC6 using ember-data and everything was OK, after migrating to ember-model one of my transitions stopped working. I narrowed down to this line:

return this.modelFor("product.images").get('images');

It seems it can't "resolve" that array and so it never makes the transition.

Everything else works:

return Ember.A([{id: 1, link: "product_1_1.jpg"}, {id: 2, link: "product_1_2.jpg"}]);

and

return App.ProductImages.find([1,2]);

My imagesis configured as follow:

images: Ember.hasMany('App.ProductImage', 'images'),

Document error handling

Our belief is that error handling should be done inside controllers off the returned promises.

Changing namespace of the libray to something else than Ember

The namespacing of Ember Model is Ember for all of the classes. This is fine as long as it does not conflicts with any Ember's methods or objects nor with any other libraries. I think that this is a good moment to discuss possible change. The library hasn't got too big user base yet, so impact will not be huge - I think that later on such thing will simply not be possible. Old names could be left as references so the library can continue working without changing the apps for some time.

Server-generated data should be communicable to the client

When persisting an object, part of which is generated on the server, the server-side-generated data doesn't seem to be able to be conveyed back to the browser. I can return the entire object with its generated data in the response, but there doesn't appear to be a way to get that to feed back into the Ember model.

For example, when someone fills out a Report, the current user needs to be listed as the author of the report and tags are generated based on keywords contained in the body. If this is all done on the server side, we need a way to let the client know what that data is. Otherwise, we'll have to duplicate that logic on the client and/or just do it in the client and trust that the client isn't lying to us.

RecordArray#reload()

Since we always return the same RecordArray from findAll, we should add a method on the RecordArray to force an update

toJSON support for Array attributes

Currently when saving a model with an EmberArray attribute, the attribute is not sent as a proper JSON array.

Example:
model.coffee

artist_ids: attr()

controller.coffee

artists = @get('model.artist_ids')
artists.push('1234')
@get('model').set('artist_ids', artists)
@get('model').save()

results in this request param:
"artist_ids"=>{"0"=>"1234", "length"=>1}
when we would expect:
"artist_ids"=>["1234"]

If we use a newly created array, the params are correct:
controller.coffee

artists = []
artists.push(artist_id) for artist_id in @get('model.artist_ids')
artists.push('1234')
@get('model').set('artist_ids', artists)
@get('model').save()

results in this request param:
"artist_ids"=>["1234"]

findMany([1]) doesn't work

Using findMany will batch the requested ids, which however has a different logic if the batch only contains one id, which results in the following issue

http://i.imgur.com/PZvrVAs.png

The problem seems to be here, I wasn't able to write a failing test for this though, but it can be simply reproduced by doing find([1]) or findMany([1])

RESTAdapter#createRecord isn't nesting the payload

I'm not sure if this is intentionally ommited, since ember-model is aiming to provide a lot more flexibility than ember-data, but the behavior is different.

App.User.create({ name: "foo" }).save()
// POST /users.json { name: "foo" }

while the convention is to do POST /users.json { name: "foo" }. Would a PR to change this be accepted? Or not change, but maybe add an extension point to the RESTAdapter so that it is easy to override how the outcoming JSON is handled?

Here's an example (from the brains of @mekishizufu)

  createRecord: function(record) {
    var url = this.buildURL(record.constructor);

    return this.ajax(url, this.paramsFor(record), "POST").then(function(data) {
      Ember.run(function() {
        record.load(data.id, data);
        record.didCreateRecord();
      });
    });
  },

  paramsFor: function(record) {
    var rootKey = get(record.constructor, "rootKey"), json = record.toJSON(),
      params = {};

    if (rootKey) {
      params[rootKey] = json;
    } else {
      params = json;
    }

    return params;
  },

where paramsFor could just return record.toJSON() by default, but would be easier to override. Right now you basically need to override every adapter method to change this.

`belongsTo` does not load model when accessed directly from nested URL resource

I've created the following JSBin that shows the issue,

http://jsbin.com/etazij/1/edit

When I access the parent ("tables") resource first by going to,

http://jsbin.com/etazij/1#/tables

and then clicking on any numbers (1 - 6), Ember Model loads the "Tab" model correctly for the "Table".

However, if I were to access the "table" resource from the URL by going to the following links directly from the browser,

http://jsbin.com/etazij/1#/tables/1
http://jsbin.com/etazij/1#/tables/2
http://jsbin.com/etazij/1#/tables/3
http://jsbin.com/etazij/1#/tables/4
http://jsbin.com/etazij/1#/tables/5
http://jsbin.com/etazij/1#/tables/6

"Tab" model fails to load for that specific "Table".

Please let me know if you need more information. Thank you!

Proper sideloading with embedded objects

Im trying to understand the proper way to sideload embedded objects. I started to use the hasMany but I believe that at the moment its still being tweaked.

In the meantime im trying to use a computed property. I just dont think im doing it correctly.

I put together a more detailed description on Stack any advise would be great

http://stackoverflow.com/q/17462341/1408461

Calling create in async route causes white screen of death

I'm likely doing something wrong, but I think this has something to do with the new async router stuff in rc6. This works:

App.DashboardsNewRoute = Em.Route.extend
    setupController: (controller) -> 
        dashboard = App.Dashboard.create()
        controller.set 'model', dashboard

But this does not:

App.DashboardsNewRoute = Em.Route.extend
    model: () -> 
        App.Dashboard.create()

I tried stepping through the router, but there doesn't seem to be anything wrong going on. Does create return a promise? Looking at the code it doesn't seem like it does anything special in init.

Any ideas?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.