Giter VIP home page Giter VIP logo

epf's Introduction

Ember.js Persistence Foundation

Build Status

Ember.js Persistence Foundation (epf) is a robust and stable framework for syncing client state with a persistent backend such as a REST API or socket connection. Defining characteristics of epf include:

  • Correctness is paramount. All other features, including performance, are important, but secondary.
  • Built around synchronization. Models are never locked and framework semantics assume updates are always coming in from a backend.
  • Full support for relationships. Related models can be saved concurrently and the framework will automatically order requests around foreign key dependencies.
  • Robust handling of conflicts and errors.
  • Forking models is first-class within the framework.
  • All operations are structured around javascript promises.

Epf is a functional alternative to ember-data and is used in production at GroupTalent with dozens of inter-related models.

Installation

For now, as epf is in development, follow the development instructions to use epf. The build-browser command will create browser-compatible distributables in the dist folder. Include epf.js in the page after ember.js.

Getting Started

Your backend

By default, epf assumes that the backend is a REST api which sticks to pretty much the same conventions as ember-data's RESTAdapter needs. There are a few differences however:

  • EPF sets a client_id in the JSON for every model and expects this to be echoed back by the server. It uses this to keep it's internal idmap up to date.
  • Related keys still need to use _id and _ids (this is different from ember-data 1.0 beta 2)

Defining Models

All models within epf are subclasses of Ep.Model. For example:

App.Post = Ep.Model.extend({
  title: Ep.attr('string'),
  body: Ep.attr('string'),

  comments: Ep.hasMany(App.Comment),
  user: Ep.belongsTo(App.User)
});

Loading Data

The primary means of interacting with epf is through a session. Epf automatically injects a primary session into all routes and controllers. To load data, you can use the load method on the session:

App.PostRoute = Ember.Route.extend({

  model: function(params) {
    return this.session.load('post', params.post_id);
  }

});

For compatibility with the behavior of the Ember.js router, a find method is also placed on models. The above code is equivalent to:

App.PostRoute = Ember.Route.extend({

  model: function(params) {
    return App.Post.find(params.post_id);
  }

});

The find method is the only method that is available on the models themselves and it is recommended to go through the session directly.

By default, Ember.js will automatically call the find method, so the above route can actually be simplified to:

App.PostRoute = Ember.Route.extend({
  // no model method required, Ember.js will automatically call `find` on `App.Post`
});

The session object also has other methods for finding data such as query.

Mutating Models

To mutate models, simply modify their properties:

post.title = 'updated title';

To persist changes to the backend, simply call the flush method on the session object.

post.title = 'updated title';
session.flush();

In epf, most things are promises. In the above example you could listen for when the flush has completed using the promise API:

post.title = 'updated title';
session.flush().then(function(models) {
  // this will be reached if the flush is successful
}, function(models) {
  // this will be reached only if there are errors
});

Handling Errors

Sessions can be flushed at any point (even if other flushes are pending) and re-trying errors is as simple as performing another flush:

post.title = 'updated title';
session.flush().then(null, function() {
  // the reject promise callback will be invoked on error
});

// do something here that should correct the error (e.g. fix validations)

session.flush(); // flush again

Models also have an errors property which will be populated when the backend returns errors.

Transactional Semantics and Forked Records

Changes can be isolated easily using child sessions:

var post = session.load(App.Post, 1);

var childSession = session.newSession(); // this creates a "child" session

var childPost = childSession.load(App.Post, 1); // this record instance is separate from its corresponding instance in the parent session

post === childPost; // returns false, they are separate instances
post.isEqual(childPost); // this will return true

childPost.title = 'something'; // this will not affect `post`

childSession.flush(); // this will flush changes both to the backend and the parent session, at this point `post` will have its title updated to reflect `childPost`

Development

To build epf, follow the instructions below:

  • Install node.
  • git clone https://github.com/getoutreach/epf
  • cd epf
  • npm install
  • npm test to run the tests via mocha
  • To build a browser distributable, run the build-browser command in the repository root with ember-script build-browser (make sure to install ember-script globally).

Discussion list

You can join the email discussion group to get help, as well as discuss new features and directions for Epf. Please post any questions, interesting things you discover or links to useful sites for Epf users.

epf's People

Contributors

ccarruitero avatar csterritt avatar dsawardekar avatar ghempton avatar ginty avatar gunn avatar heartsentwined avatar jasonkriss avatar jimsynz avatar lastobelus avatar machty avatar stas-sl avatar taras 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  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

epf's Issues

How to de-serialize model attributes manually

Hi,

Firstly thanks a lot for EPF, using ember was really getting to be a bit demoralizing due to ED not obviously going to fix the problems any time soon, so it's great to see some momentum building up here with what looks to be a great ORM library.

Also this is not really an issue but asking it here until a better forum for things like this emerges.

So its cool that with EPF you can do:

$.get("some/custom/endpoint", function(data){
    user = App.User.create(data.user);
    session.merge(user); 
});

However one problem with this is that the json is not de-serialized as it would be if the record was loaded though the session interface.
i.e. {first_name: "Stephen"} does not get assigned to the model attribute "firstName".

Is there a way to call the de-serializer manually?

user = App.User.create(magic.deSerializer(App.User, data.user));

Some belongs_to relationship changes not persisted

Given the following relationship:

App.User = Ep.Model.extend
  profile: Ep.belongsTo("profile")

App.Profile = Ep.Model.extend
  users: Ep.hasMany("user")

If a user already has a profile, then this works:

user.get("profile")    # -> profileA
user.set("profile", profileB)

session.flush()  # -> PUT(user) is made to server for the change

However when the profile is starts off from undefined, then no PUT occurs:

user.get("profile")    # -> undefined
user.set("profile", profileA)

session.flush()  # -> NO PUT(user) is made to server

Similarly changing the profile to null does not update either:

user.get("profile")    # -> profileA
user.set("profile", null)

session.flush()  # -> NO PUT(user) is made to server

Defining relationships with strings

I noticed that your examples define relationships by passing the actual model classes as arguments.

App.Post = Ep.Model.extend({
  comments: Ep.hasMany(App.Comment),
  user: Ep.belongsTo(App.User)
});

This is different than the Ember Data examples that define relationships with strings like this.

App.Post = DS.Model.extend({
  comments: DS.hasMany('App.Comment'),
  user: DS.belongsTo('App.User')
});

Can relationships be created using strings to represent the related models?

Request for Sample Projects

Please publish , TODO & Blog examples ported to epf. Also please include a example involving 3level nested route.

Manually loading external data

The example of loading data in the Ember.js Persistence Foundation video (20:10) shows the code below for loading AJAX retrieved data into the session.

$.getJSON('/users/1', function(data) {
  var user = App.User.create(data);
  session.merge(user);
});

Unfortunately this won't work as expected if any of the attributes of App.User are not strings.

I'm currently working around this using the code below.

$.getJSON('/users/1', function(data) {
  var user = session.get('adapter.serializer').deserializeModel(App.User, data);
  session.merge(user);
});

While this works for deserializing the attributes properly, it seems messy to have to reach into the session.

I've started experimenting with having session.create() use the deserializer, but I'm not sure if this is the best approach.

created model overwrites subsequent changes

My app is setup to create a new model once it has data in a textfield, and to autosave changes.
I accomplish this by using Ember.run.debounce to flush the session.

I am passing through clientId and clientRev.

When I start typing in the text field, a new record is created. When the create finishes, epf overwrites subsequent changes -- so everything typed after the create gets fired gets deleted. It's easy to make the problem very obvious by adding a couple second delay on the backend.

I have tried using a child session or not, it doesn't seem to make a difference.

Update seems to work fine, although I'm confused how, since client_id & client_rev aren't passed through on updates.

Tackle Child Session problems

Hi @ghempton,

I selected EPF as my persistence layer after using Ember Data and Ember Model. I've done some work to integrate it with my backend #48 but now I'm bumping into issues related to Child Sessions that are described in #22 and #49. I was hoping to use EPF during the Toronto Music Hack Day but I'll probably need this functionality working.

If you don't have plans to address this before the weekend, would you mind if I took a crack at it?

I already cloned the project and downloaded the code. My plan would be to start off by writing failing tests that cover the cases that need to be addressed. I looked at the tests and I can see that they're written in EmberScript. Do I need to learn EmberScript to write these tests? or can I just write regular JS?

Is this something that someone who is new to EPF can try to tackle in a few days or does it require your attention?

What do you think?

Taras

Models with numbers not round tripping to the server

We've got a simple app with a simple model with a number attribute (age). When we create a new object, the property with a number attribute is not being sent to the server.

App.Foo = Ep.Model.extend
  name: Ep.attr('string')
  age: Ep.attr('number')

In addition, when we query data from the server, We can see the data in the server's json response, but that data does not flow through to the template.

Any ideas?

Can it be used without Emberjs

I have been looking for a generic javascript datasource framework much of what EPF does. basically I was looking for

  1. defining models
  2. datatype defined for each attribute
  3. Conversion from server string to formatted output currency/dates/numbers
  4. Serialization of formatted strings back to model attribute datatype
  5. model list
  6. Sorting of model list/serverside
  7. filtering of model list/serverside
  8. child parent relationships
  9. observable models and lists
  10. Pagination support

So that this datasource can be used by GUI components like tree grids and comboboxes autocompletes dynamic forms etc.

deserializeEmbeddedBelongsTo tries to createModel() with a string...

I have problems deserializing embedded relationships.

Up to now I could figure out that deserializeEmbeddedBelongsTo() which calls deserializeModel receives a string as type.
deserializeModel() in turn tries to .create() the model but still fail (because it is typeof string).

I configured the RestAdapter to always embed the one model in the other one..

Ep.RestAdapter.map('App.Gear', {
     user: { embedded: 'always' }
});

Did I miss some configuration?

Hope for a Pagination Implementation?

Huge portion of Ember Data, and similar projects, is no proper access to Pagination.

What are we supposed to do, when Model.find() returns 1000+ records?

Any word on EPF implementing some sort of a Pagination method?

Model with no attributes set

this.session.add(App.Post.create())
this.session.flush()

throws an error:
Uncaught TypeError: Cannot call method 'toString' of undefined

But

this.session.add(App.Post.create({}))
this.session.flush()

works just fine

This line creates the error:
_typeToString: function (type) {
return type.toString().split('.')[1].underscore();
// Uncaught TypeError: Cannot call method 'toString' of undefined
}

Rollback or Cancel behaviour

I have a cancel button in an edit view and I'd like to cancel all changes when that button is pressed.

Do I manipulate the session or the model to rollback the changes?

Model with name ending with "s" not properly retrieved

Hi, i'm currently using epf with a lot of models and face a strange bug,

if i have a company and companyHasAddress models

Ep.RestAdapter.configure('App.Company', {
  sideloadsAs: 'companies'
});

Ep.RestAdapter.configure('App.CompanyHasAddress', {
  sideloadsAs: 'company_has_addresses'
});

Ep.RestAdapter.configure('plurals', {    
    company: 'companies',
    company_has_address: 'company_has_addresses'
});

App.Adapter = Ep.RestAdapter.extend({
    namespace: 'api'
});

App.Company = Ep.Model();
App.CompanyHasAddress = Ep.Model();

App.Company.reopen({
  companyHasAddresses: Ep.hasMany(App.CompanyHasAddress, {inverse: 'company'})
});

App.CompanyHasAddress.reopen({
  company: Ep.belongsTo(App.Company)
});

in my route i ve got an event that handle the creating process for a companyHasAddress

createAddress: function(newAddress) {
  var session, childSession, company, companyHasAddress, flashMessenger;
  session = this.session;
  childSession = session.newSession(); 
  company = this.controller.get('model');
  companyHasAddress = childSession.create(App.CompanyHasAddress, newAddress);
  childSession.flush().then(function(models) {
    company.get('companyHasAddresses').pushObject(companyHasAddress);
  }, function(errors) {
    var xhr = companyHasAddress.get('errors.xhr');
  });
}

epf is looking for company_has_addresses hash key,

 { "company_has_addresses": { ... address data }}

but theorically the expected response should be

 { "company_has_address": { ... address data }}

if i return the expected company_has_address hash key error show up has the hash key as no corresponding mapping,
if i return the expected company_has_addresses hash key then method is never firered probably cause company_has_address hash key is expected for a post to resolve or reject the promise.

i will look at the test of epf to see if i am able to produce some for demonstrate the issue.

ps: i m not returning the clientID from the server, i use the proposed patch at issue #23

psยฒ: i m not sure it s an epf or an ember-data issue

model names aren't resolving properly

In a route I need to call model 'fooModel' rather than 'foo' to gain access to it. It seems that a nameResolver functionality is missing in the current epf implementation that would address this issue.

ArrayController binding to all models does not get notified of a newly created model

I have an Em.ArrayController with the model property bound in a route like this:

  model: function() {
    return this.session.query('foo');
  }

Now, to create a new Foo I have the following action on the App.FoosController:

App.FoosController = Ember.ArrayController.extend({
  newFoo: function() {
    this.session.create(App.Foo, {name: "New Foo"});
    this.session.flush();
  }
});

However, App.FoosController's model property does not change (but it does using Ember Data). For this to work, I need to manually add the object to the controller's model (e.g. this.get('model').addObject(foo)).

Is it possible to do this automatically?

How to extend serializer and deserializer.

My json structure does not confirm with the Ember Data or epf format.
Plurals json is

{
  "total_pages": 1,
  "objects": [
    {
     "id" : "1",
     "name": "name1",
     "child" : []
    },
    {
     "id" : "2",
     "name": "name2",
     "child" : []
    }
   ]
}  

Singular json is

{
  "id" : "1",
  "name": "name1",
  "child" : []
}

session.flush not issuing PUT

Problem
The call to session.flush() does not persists changes to the backend. The promise resolves immediately, and no request is sent.

I don't know if I do something wrong or EPF does not work.

What I do
I create a child session in the router using this code:

    var childSession = this.session.newSession();
    controller.set('newComp', childSession.add(App.Company.create({
      financialPeriodDuration: 12,
      /* fields */
    })));
    controller.set('newFY', childSession.add(App.FinancialYear.create({
      company: controller.get('newComp')
    })));

I also tried with the other syntax childSession.create('company', { ....
I also tried by not creating a child session (replacing this.session.newSession() with this.session).

Then, after setting properties on these objects through a form, I add an account to the company using this code, from another controller

      var session = this.get('controllers.companiesAdd.newComp.session');
      var comp = this.get('controllers.companiesAdd.newComp');
      var account = session.create('account', {
        company: comp,
        /* fields */
      });
     /* EDIT: I thought this part would not be useful, but maybe it is: */
     if (!comp.get('mainBankAccount')) {
        comp.set('mainBankAccount', account);
      }
     /* END of edit */

Finally I flush, using this code, from the 'companiesAdd' controller:

      this.get('newComp.session').flush().then(function (val) {
        console.log(val);
      });

the value returned in the promise is:
[isEnumerable: true, nextObject: function, firstObject: undefined, lastObject: undefined, contains: functionโ€ฆ]. It has a length: 0 attribute. It should be a list of updated models, right?

Other info
I tried looking at EPF internals in the debugger, and in the childSession flush method, I could see my three objects newComp, newFY and account in the dirtyObjects collection. But I could not track them inside the bowels of EPF.

I though that if EPF does something, it goes in Operation.perform() (am I right?) so I placed a breakpoint in here but it was never reached.

I just migrated my models from Ember Data, so there may be some configuration problem. Its works for reading records from the server, though.

Thanks in advance for your help. EPF seems very promising, and I would be very pleased to be able to use it.

Fixtures

Will it have FixtureAdapter for testing?

The type passed to Ep.belongsTo must be defined ember.js?

I am using emberjs with rails. just converted model to use epf, but encountered:

Assertion failed: The type passed to Ep.belongsTo must be defined ember.js?body=1:364
(anonymous function) ember.js?body=1:364
Ember.assert ember.js?body=1:52
Ep.belongsTo epf.js?body=1:1649
(anonymous function) avatar.js?body=1:12
(anonymous function)

But I have defined the type:

Yu.Avatar = Ep.Model.extend
active: Ep.attr('boolean')
image: Ep.attr('string')
cropX: Ep.attr('number')
cropY: Ep.attr('number')
cropW: Ep.attr('number')
cropH: Ep.attr('number')

imageMediumUrl: Ep.attr('string')
imageLargeUrl: Ep.attr('string')
canManage: Ep.attr('boolean')

user: Ep.belongsTo(Yu.User)

Yu.User = Ep.Model.extend
email: Ep.attr('string')

avatar: Ep.belongsTo(Yu.Avatar)
basicinfo: Ep.belongsTo(Yu.Basicinfo)

Thanks.

New Records in a Child Session Don't Stay There

It has always seemed annoying to have to clear up un-saved models when leaving a create route and I thought that the child session capabilities for EPF would simplify this considerably.

e.g. with code like this the new object could be created upon entering the route, and if the route is left via any means other than the save action the child session and it's contained object would just get forgotten about and garbage collected.

model: ->
  @childSession = @session.newSession()
  @childSession.create(App.Foo)

events:
  save: ->
     @childSession.flush().then ->
        # Leave upon successful save

However this doesn't work because every time the child is flushed the new record is put into the parent session before trying to persist it. If this fails then an unsaved record is left in the parent session and not isolated to the child as I would have expected.

Is this a bug or are my expectations wrong?

Thanks!

How To Debug the Tests

Not really an issue but I don't know where else to ask...

I have a small bug fix I would like to submit and I've added a test for it, however I can't get the test to work properly.

I've never used this testing framework before and I'm not sure how to go about debugging it. I've tried googling but honestly I'm not really sure what I should be searching for.

Could someone give me a pointer as to how to go about setting a breakpoint to inspect and step through the test execution?

Thanks!

Reserved model attribute names

Hi Gordon

It seems that Epf have some reserved attribute names used internally on Ep.Model.
For example, while migrating from Ember Data, I have a model with an attribute 'type'. It causes a "Call to underscore on 'undefined' " error, and take me a few minutes to figure it out. I have just renamed my attribute to something else than 'type', and all turns good.

Could you confirm that, and eventually document those reserved words ? I think about 'id', 'clientId', 'type', 'session', etc.

Cheers
Rรฉmy

Polymorphism

Are polymorphic relationships supported ? If so what is the syntax for that

inject models from known json

Use case: pre-load static model data from embedded json in js files.

Ember-data version

class App.Store extends DS.Store
  init: ->
    super.apply this, arguments
    @loadMany(App.Foo, '[{"id":1, "value":"a"}, {"id":2, "value":"b"}]')

Epf equivalent = ?

belongsTo-relationship returning Ep.LazyModel

I'm not sure if this is a bug or just improper use on my side, but I have the problem that for a belongsTo relationship, the associated model never turns from an Ep.LazyModel instance to a proper (e.g. App.Foo) instance.

The JSON just serves the id, e.g. {foo_id: 42}.

Any hints?

clearing datastore / session

Basically a duplicate issue / feature request of emberjs/data#235 - is there a way to clear the loaded models of epf? Or of a session?

The closest I can find about this functionality in epf: (as far as I can understand the code,) creating a child session and loading all models into it, then discarding the session, won't "discard" the models themselves. Subsequent calls to load(Model, id) will still return the previously loaded model from client side.

Use case: authenticated app. On sign out, one expects the models loaded into the previous signed in session to be inaccessible in any way. An even better approach would be to allow devs to specify which models are cleared, and which are not, somewhere in model declarations. That way, "public" / "common" models like product can be preserved and cached; while "private" models like cart can be destroyed.

Current workaround is to force a browser reload on sign out, hence destroying the ember app altogether, and restarting it.

Checking if model is dirty

How do I check if a model is dirty? I am trying to see if a model in a child session is dirty by doing something like model.isDirty or model.get('isDirty') which is returning undefined. The only way I can check is by doing model.session.get("dirtyModels").contains(model) which feels wrong and I am trying to observe the isDirty property like I did in ember-data.

Thanks for making epf!

Query data not resolving correctly

Hi,

I've turned this bit of ED code:

controller.set("content", App.User.find(type: "staff"))

into this EPF code:

controller.set("content", @session.query(App.User, type: "staff"))

The correct request is fired off and from stepping through the code execution I can see that the models get loaded in to an array correctly.

However the @session.query method ends up returning a promise and Ember does not like it, it leads to some obscure errors like:

Uncaught TypeError: Object [object Object] has no method 'slice' 

which occur when Ember is trying to sort the content.

Is there anything wrong with my code?

Thanks!

A simple working example

Hi,

Could you please post a simple working example of how to integrate EPF with Ember.js

Thanks.

Consistent id-to-string coercion

From #7 at comment:

// works
session.merge(App.Post.create({id: '1', title: 'testing'}));

// doesn't work
session.merge(App.Post.create({id: 1, title: 'testing'}));

Numeric id should also be accepted. The current behavior doesn't parallel

session.load(App.Post, 1); // works with numeric argument!

The behind-the-scenes coercion of id to string could be consistently applied everywhere. Perhaps a full code-review needed to search for all such cases.

Ep.LazyModel breaks {{#linkTo}} in deep relationships

The linkTo helper breaks when I want to link to a model in a HABTM relationship with an explicit join model. It looks like since the join model is lazy loaded, the property for which the linkTo helper needs to create the route does not even exist.

Consider the following example:

App.Article = Ep.Model.extend({
  title: Ep.attr('string'),
  authorships: Ep.hasMany(App.Authorship)
})
App.Authorship = Ep.Model.extend({
  authorships: Ep.hasMany(App.Authorship),
  person: Ep.hasMany(App.Person)
})
App.Person = Ep.Model.extend({
  name: Ep.attr('string'),
  authorships: Ep.hasMany(App.Authorship)
})

articles/show.hbs

{{#each authorships}}
  {{! This does not work:}}
  {{#linkTo "people.show" person}}{{person.name}}{{/linkTo}}
  {{! This works, but looks unnecessarily ugly}}
  {{#if person.isLoaded}}
    {{#linkTo "people.show" person}}{{person.name}}{{/linkTo}}
  {{/if}}
{{/each}}

I get the following errors:

Assertion failed: Cannot call get with 'id' on an undefined object. 
Uncaught TypeError: Cannot read property '__ember1375951619363_meta' of undefined

It seems like that since in the article's view, the authorship is still a lazy model, a lookup of the person property of the lazy model fails and the route generation fails as well.

Any ideas?

This is possibly related to issue #34.

session is not defined

<!DOCTYPE html>
<html>
<head>
 <title>EmberJS epf</title>
 <meta charset="utf-8"/>
</head>
<body>  
  <script src="js/libs/jquery-1.9.1.js"></script>
  <script src="js/libs/handlebars-1.0.0-rc.4.js"></script>
  <script src="js/libs/ember-1.0.0-rc.6.js"></script>
  <script src="js/libs/epf.js"></script>
  <script type="text/javascript">
  (function(){
   //epf version 0.1.1
    var attr = Ep.attr, hasMany = Ep.hasMany, belongsTo = Ep.belongsTo;
    var App = window.App = Ember.Application.create();
    App.Project = Ep.Model.extend({
      name: attr('string'),
      updated: attr('string'),
      created: attr('string'),
      sprints: hasMany('App.Sprint')
    });

    App.Sprint = Ep.Model.extend({
      name: attr('string'),
      updated: attr('string'),
      created: attr('string'),
      project: belongsTo('App.Project')
    });
  })();
  </script>
</body>
</html>

In chrome browser console, when I type session. I get this error:

ReferenceError: session is not defined
get stack: function () { [native code] }
message: "session is not defined"
set stack: function () { [native code] }
__proto__: Error

this.session output is undefined

What might be wrong here?

Load data into the epf session from an AJAX request and does the RESTAdapter make calls to custom rails controller actions

1. How to do I add data either into a new epf session or add the data to an existing session after making an AJAX request like the one shown below. Also is that the right way to access a session from the controller. Do I use session.merge or will it be session.load

App.MyController = Ember.ArrayController.extend({

   foo: function() {

       $.post("/signIn", function(data) {
          // here is where I want to load the data into the session
         this.get('session').load(App.User, data.users);

          //or do I use
         this.session.merge(App.User, data.users)
       });
    }
});

or do I use this.adapter.load(type, id) as in this.get('session').adapter.load(App.User, data.users) I got that by reading the source code link below:

https://github.com/GroupTalent/epf/blob/master/lib/session/session.js#L224

2. Does the RESTAdapter make calls to custom rails controller actions.

After reading the source here:

https://github.com/GroupTalent/epf/blob/master/test/rest/rest.rpc.em#L20

It seems that is what the remoteCall method does, but I am correct?

If I am correct, will this be the right way to update the rails controller action to true using remoteCall where the url is tasks/:id/complete. The rails controller code is pasted after the epf code below:

   session.remoteCall(task, submit: 'true')

This is the rails controller with an action called complete:

 class TasksController < ApplicationController
   def complete
     @task = @list.tasks.find(params[:id])
     @task.completed = true
     @task.save
   end
 end

The rails route could look like this or the one underneath:

match 'tasks/:id/complete' => 'tasks#complete'

In the database, complete is a boolean:

create_table "tasks", :force => true do |t|
  t.boolean "completed", :default => false  
end

Persisting hasMany relationships example

I am having trouble persisting a hasMany relationship and wanted for someone to provide a non-trivial example.

My models:

App.Release = Ep.Model.extend
    name: Ep.attr('string')

App.TestCase = Ep.Model.extend
    name: Ep.attr('string')
    description: Ep.attr('string')

App.Release.reopen
  releases: Ep.hasMany(Pixelmark.Release) 
  test_cases: Ep.hasMany(Pixelmark.TestCase)

App.TestCase.reopen
  releases: Ep.hasMany(Pixelmark.Release)

Say I have a bunch of TestCases that already exist. Some are assigned to a Release, some are not, and none of them are newly created models on the client. Say I have two model instances, release, and test_case. To assign a test case to a release, I am trying to do release.get('test_cases').addObject(test_case)

If I flush the session, nothing happens because nothing gets dirtied in the process. What is the proper way to persist this relationship? My models are sideloaded and not embedded, but do have a reference to the ids (the api returns an array of test_case_ids). I also have no problem reading the data (everything populates correctly with the sideloading), just persisting it back.

In think this would be very useful example for people to see.

Does not load model

This is my code:

App = Ember.Application.create();
App.ApplicationView = Ember.View.extend();

App.Router = Ember.Router.extend();

App.Router.map(function() {
});

var serializer = Ep.RestSerializer.extend();

App.Adapter = Ep.RestAdapter.extend({
namespace: 'api',
serializer: serializer
});

App.Post = Ep.Model.extend({
title: Ep.attr('string')
});

App.IndexRoute = Ember.Route.extend({
model: function() {
this.session.load('post')
},
renderTemplate: function() {
//console.log(this.session.load(App.Post))
this.render('index');
}
});

I get these two errors in the Chrome console:
Error while loading route: TypeError {}
Uncaught TypeError: Cannot call method 'toString' of undefined

Dependency of client_id response on POST

Took me a while to figure out that a POST requires a response of the client_id that was assigned to the model in the JSON, otherwise Epf throws the following error which doesn't tell you anything: "Cannot call method 'toString' of undefined"

I never knew that this was necessity, and this definitely wasn't the case with ember-data as far as I can tell. It would be great if this error could be more descriptive and if the docs could include that your server in fact needs to respond with the model's client_id on POST.

What is happening now is that a new (duplicate) model instance gets created with a brand new id in response to the JSON because Epf thinks its a new model, and the success callback never fires on flush even though the POST is a success.

Let me know if I could be of any help, great project!

Error querying single model - type seems undefined

I'm working on a pretty minimal EPF implementation to see if I want to use it for persisting collaborated-on objects in a highly-customized CMS sorta thing. Right now, I'm just tracking user state - users logged in at the admin level can see /api/users, and all users can see /api/users/:user_id, for their own userid.

I'm getting the error listed at the bottom of this post in my code as part of my initial setup. Here's the profile route:

Social.ProfileRoute = Ember.Route.extend
  model: ->
    console.log 'getting usercontroller'
    ret = @session.load 'user', 1
    console.log ret
    console.log 'got it'
    ret

and the user object:

Social.User = Ep.Model.extend
  # type: 'Social.User'
  name: Ep.attr('string')
  email: Ep.attr('string')

This is just a test, with a super-simple user object. I've tried renaming name to uname (in case I was conflicting with an internal) and (as you can see) manually setting type on the object itself, but neither of these approaches works.

If I instead do the model as a list (everything in /api/users), I can see that the correct number of items works {{model.length}} renders as 3, but any attempt to individually address User objects fails with the same error below, either by getting them manually out of the list or by getting them singly as a load 1 instead of a query.

I feel like I'm overlooking something super-simple here - the foo/bar sample EPF app runs fine on my system, and I really can't tell what I'm not doing right here.

Uncaught TypeError: Cannot call method 'toString' of undefined epf.js?body=1:2765
Ep.Adapter.Ember.Object.extend._typeToString epf.js?body=1:2765
Ep.Adapter.Ember.Object.extend._generateClientId epf.js?body=1:2762
Ep.Adapter.Ember.Object.extend.reifyClientId epf.js?body=1:2750
Ep.Session.Ember.Object.extend.reifyClientId epf.js?body=1:3322
Ep.Session.reopen.merge epf.js?body=1:2980
(anonymous function) epf.js?body=1:3251
invokeCallback ember.js?body=1:7252
(anonymous function) ember.js?body=1:7302
EventTarget.trigger ember.js?body=1:7075
(anonymous function) ember.js?body=1:7365
DeferredActionQueues.flush ember.js?body=1:4727
Backburner.end ember.js?body=1:4813
(anonymous function)

Documentation on Usage

Hi there,

I like the sound of EPF, but I'm a bit confused by the docs. For example, where/how do I specify the URL endpoint of the REST backend? The equivalent of ember-data :

  App.Store = DS.Store.extend
    adapter: DS.RESTAdapter.create
      url: '/api/v1'

Maybe inclusion of a super-simple example might help?

Error when side loading nested resource

Thanks for releasing Epf. It looks very promising.

I am having a problem with side loading comments for a posts model. I am using the library compiled from master with build-browser.

The /posts requests goes out and returns a 200, but the route throws the following exception.

Uncaught TypeError: Cannot read property 'length' of undefined epf.js:990
  Ep.Model.reopenClass.inverseFor epf.js:990
  Ep.HasManyArray.Ep.ModelArray.extend.arrayContentWillChange epf.js:1188
  superWrapper ember.js:1079
  Ember.ArrayProxy.Ember.Object.extend.arrangedContentArrayWillChange ember.js:12322
  sendEvent ember.js:2096
  Ember.Array.Ember.Mixin.create.arrayContentWillChange ember.js:9622
  Ember.Mixin.create.replace ember.js:12749
  Ember.ArrayProxy.Ember.Object.extend.replaceContent ember.js:12115
  Ep.HasManyArray.Ep.ModelArray.extend.replaceContent epf.js:1180
  superWrapper ember.js:1079
  Ember.ArrayProxy.Ember.Object.extend._replace ember.js:12231
  Ember.ArrayProxy.Ember.Object.extend._insertAt ember.js:12245
  Ember.ArrayProxy.Ember.Object.extend.pushObject ember.js:12289
  superWrapper ember.js:1079
  Ember.MutableArray.Ember.Mixin.create.addObject ember.js:10343
  (anonymous function) ember.js:10004
  Ember.EnumerableUtils.forEach ember.js:1562
  Ember.MutableEnumerable.Ember.Mixin.create.addObjects ember.js:10004
  Ep.Serializer.Ember.Object.extend.deserializeLazyHasMany epf.js:500
  Ep.Serializer.Ember.Object.extend.deserializeHasMany epf.js:487
  (anonymous function) epf.js:476
  (anonymous function) epf.js:1094
  (anonymous function) ember.js:2888
  OrderedSet.forEach ember.js:2731
  Map.forEach ember.js:2886
  Ep.Model.reopenClass.eachRelationship epf.js:1093
  Ep.Model.reopen.eachRelationship epf.js:1105
  Ep.Serializer.Ember.Object.extend.deserializeRelationships epf.js:474
  Ep.Serializer.Ember.Object.extend.deserializeModel epf.js:453
  Ep.JsonSerializer.Ep.Serializer.extend.deserialize epf.js:242
  superWrapper ember.js:1079
  Ep.RestAdapter.Ep.Adapter.extend.processData epf.js:2058
  Ep.RestAdapter.Ep.Adapter.extend.didReceiveDataForFind epf.js:2047
  Backburner.run ember.js:4623
  Ember.run ember.js:5112
  (anonymous function) epf.js:1988
  invokeCallback ember.js:7096
  (anonymous function) ember.js:7139
  EventTarget.trigger ember.js:6927
  (anonymous function) ember.js:7194
  DeferredActionQueues.flush ember.js:4901
  Backburner.end ember.js:4591
  (anonymous function) ember.js:4796

My models are,

App.Post = Ep.Model.extend
  title: Ep.attr('string')
  body: Ep.attr('string')

  comments: Ep.hasMany('App.Comment')

App.Comment = Ep.Model.extend
  author: Ep.attr('string')
  body: Ep.attr('string')

  post: Ep.belongsTo('App.Post')

And the model hook on the PostsRoute is,

App.PostsRoute = Ember.Route.extend
  model: () ->
    return this.session.query('post')

My server-side implementation is with active_model_serializers.

class PostSerializer < ActiveModel::Serializer
  attributes :id, :title, :body
  has_many :comments, embed: :ids, include: true
end

which gives the following json for /posts.

{
    "comments": [
        {
            "id": 1,
            "author": "me01",
            "body": "comment-body"
        }
    ],
    "posts": [
        {
            "id": 2,
            "title": "lorem04",
            "body": "post-body",
            "comment_ids": [
                1
            ]
        }
    ]
}

Is there anything wrong with my json output. Can you please post sample json that Epf expects for this scenario.

Thanks.

URL Aliasing for RESTFul models

Use case: I have a /api/users/:user_id resource that serves up relevant JSON about users of my system. I also make use of the session store to keep track of which user_id the current user is logged in as, and serve that up as /api/users/current.

The user object JSON includes an id field, but either because the id in the API path (which I retrieve by doing a @session.find 'user', 'current') is a string, or doesn't match the actual id field in the returned JSON, the system chokes with an error.

Uncaught TypeError: Cannot call method 'toString' of undefined epf.js?body=1:2765
Ep.Adapter.Ember.Object.extend._typeToString epf.js?body=1:2765
Ep.Adapter.Ember.Object.extend._generateClientId epf.js?body=1:2762
Ep.Adapter.Ember.Object.extend.reifyClientId epf.js?body=1:2750
Ep.Session.Ember.Object.extend.reifyClientId epf.js?body=1:3322
Ep.Session.reopen.merge epf.js?body=1:2980
(anonymous function) epf.js?body=1:3251
invokeCallback ember.js?body=1:7252
(anonymous function) ember.js?body=1:7302
EventTarget.trigger ember.js?body=1:7075
(anonymous function) ember.js?body=1:7365
DeferredActionQueues.flush ember.js?body=1:4727
Backburner.end ember.js?body=1:4813
(anonymous function)

Creating a custom adapter or adapter for deployd

deployd.com is a tool that provides RESTful APIs for custom models. I'd like to use it but has a slightly different API layout than Ember Data expects. For example, the returned JSON is not wrapped in hash, ie:

[
{ "id": "30", "name": "Hello World!" }
]

Not

{ 
    "items": [
        { "id": 30, "name": "Hello World!" } 
   ]
}

Are there any considerations for this in EPF? anything in particular that is relevant to building a custom adapter for this kind of an API?

session.create() issue / example

I'm trying to make a simple new page for a model, but I cannot get session.create() to work. Not sure if I'm using epf wrongly, or if this is an issue. Code (in ember-script):

class App.FoosNewRoute extends Em.Route
  model: ->
    @session.newSession().create Foo.Task

  events:
    save: ->
      @context.session.flush()

Observation: calling save will only flush if foos.new route is entered directly from url. If I enter from, say, foos.index and transitionTo foos.new, now calling save will not flush anything (the promise is completed, but it does nothing). If I am in foos.new in the first place, then transitionTo another route away, and then goes back to foos.new, same observation (nothing flushed).

Fixture support

Thank you for releasing EPF. It looks very promising as a Ember.Data replacement.

I read the documentation, but couldn't find support for Ember Data's fixtures. Is there a similar concept in EPF?

Flushing child session merges child model with parent model even if PUT request failed

I am following the following convention from the docs:

var post = session.load(App.Post, 1);

var childSession = session.newSession(); // this creates a "child" session

var childPost = childSession.load(App.Post, 1); // this record instance is separate from its corresponding instance in the parent session

post === childPost; // returns false, they are separate instances
post.isEqual(childPost); // this will return true

childPost.title = 'something'; // this will not affect `post`

childSession.flush(); // this will flush changes both to the backend and the parent session, at this point `post` will have its title updated to reflect `childPost`

If the PUT request fails after doing childSession.flush(), post is still updated to match the changes to childPost.

I am using a child session to update a model (it is a user settings page) so it doesn't change in real time in another part of the UI. This is very easy using child sessions and child models (compared to ember-data), but the changes shouldn't merge if the backend PUT fails.

Any workarounds?

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.