GithubHelp home page GithubHelp logo

orbitjs / ember-orbit Goto Github PK

View Code? Open in Web Editor NEW
316.0 17.0 42.0 5.06 MB

Ember.js data layer built with Orbit.js

License: MIT License

JavaScript 12.42% HTML 0.87% TypeScript 86.08% Handlebars 0.47% CSS 0.16%

ember-orbit's Introduction

ember-orbit

Join the chat at https://gitter.im/orbitjs/orbit.js

ember-orbit (or "EO") is a library that integrates orbit.js with ember.js to provide flexibility and control in your application's data layer.

Highlights

  • Access to the full power of Orbit and its ecosystem, including compatible sources, buckets, and coordination strategies.

  • A data schema that's declared through simple model definitions.

  • Stores that wrap Orbit sources and provide access to their underlying data as easy to use records and record arrays. These stores can be forked, edited in isolation, and merged back to the original as a coalesced changeset.

  • Live-updating filtered query results and model relationships.

  • The full power of Orbit's composable query expressions.

  • The ability to connect any number of sources together with Orbit coordination strategies.

  • Orbit's git-like deterministic change tracking and history manipulation capabilities.

Relationship to Orbit

EO provides a thin "Emberified" layer over the top of some core primitives from Orbit, including Store, Cache, and Model classes. Most common developer interactions with Orbit will be through these classes.

EO does not attempt to wrap every base class from Orbit. For instance, you'll need to use Orbit's Coordinator and coordination strategies to define relationships between Orbit sources. In this way, you can install any Orbit Source or Bucket library and wire them together in your EO application.

Important: It's strongly recommended that you read the Orbit guides at orbitjs.com before using EO, since an understanding of Orbit is vital to making the most of EO.

Compatibility

  • Ember.js v3.24 or above
  • Ember CLI v3.24 or above
  • Node.js v12 or above

Status

EO obeys semver and thus should not be considered to have a stable API until 1.0. Until then, any breaking changes in APIs or Orbit dependencies should be accompanied by a minor version bump of EO.

Demo

todomvc-ember-orbit is a simple TodoMVC example that uses EO to illustrate a number of possible configurations and application patterns.

Installation

Install EO in your project with:

ember install ember-orbit

The generators for orbit sources and buckets will attempt to install any additional orbit-related dependencies.

Usage

EO creates the following directories by default:

  • app/data-buckets - Factories for creating Orbit Buckets, which are used to save / load orbit application state.

  • app/data-models - For EO Model classes, which represent data records.

  • app/data-sources - Factories for creating Orbit Sources, which represent different sources of data.

  • app/data-strategies - Factories for creating Orbit coordination strategies.

Note that "factories" are simply objects with a create method that serves to instantiate an object. The factory interface conforms with the expectations of Ember's DI system.

EO installs the following services by default:

  • store - An ember-orbit Store to manage querying and updating models.

  • dataCoordinator - An @orbit/coordinator Coordinator that manages sources and coordination strategies between them.

  • dataSchema - An @orbit/data Schema that represents a schema for models that is shared by the store and other sources.

  • dataKeyMap - An @orbit/data KeyMap that manages a mapping between keys and local IDs for scenarios in which a server does not accept client-generated IDs.

All the directories and services configured by EO can be customized for your app, as described in the "Customizing EO" section below.

Defining models

Models are used to access the underlying data in an EO Store. They provide a proxy to get and set attributes and relationships. In addition, models are used to define the schema that's shared by the sources in your Orbit application.

The easiest way to create a Model class is with the data-model generator:

ember g data-model planet

This will create the following module in app/data-models/planet.js:

import { Model } from 'ember-orbit';

export default class Planet extends Model {}

You can then extend your model to include keys, attributes, and relationships:

import { Model, attr, hasOne, hasMany, key } from 'ember-orbit';

export default class Planet extends Model {
  @key() remoteId;
  @attr('string') name;
  @hasMany('moon', { inverse: 'planet' }) moons;
  @hasOne('star') sun;
}

You can create polymorphic relationships by passing in an array of types:

import { Model, attr, hasOne, hasMany } from 'ember-orbit';

export default class PlanetarySystem extends Model {
  @attr('string') name;
  @hasMany(['moon', 'planet']) bodies;
  @hasOne(['star', 'binaryStar']) star;
}

Stores and Caches

EO's Store class is a thin wrapper around Orbit's MemorySource, while EO's Cache class wraps Orbit's MemoryCache. The difference between memory sources and caches is explained extensively in Orbit's docs.

The essential difference between EO's Store and Cache and the underlying Orbit classes is that EO is model-aware. Unlike plain Orbit, in which results are returned as static POJOS, every query and update result in EO is translated into Model instances, or simply "records". When changes occur to the underlying Orbit sources and caches, they will be reflected immediately in EO's records.

Every EO record is connected to a cache, which in turn belongs to a store. When stores or caches provide results in the form of records, they are always instantiated by, and belong to, a Cache. For a given identity (type / id pair), there is only ever one record instance per cache. These are maintained in what is fittingly called an "identity map".

Records, including all their attributes and relationships, will stay in sync with the underlying data in their associated cache.

Querying Data

There are three primary methods available to query records:

  • store.query() - returns a promise that resolves to a static recordset.

  • store.cache.query() - returns a static set of in-memory results immediately.

  • store.cache.liveQuery() - returns a live recordset that will be refreshed whenever the data changes in the cache.

All of these query methods take the same arguments as any other queryable Orbit source - see the Orbit guides for details.

The following liveQuery immediately returns a live resultset that will stay updated with the "terrestrial" planets in the store:

let planets = store.cache.liveQuery((qb) =>
  qb
    .findRecords('planet')
    .filter({ attribute: 'classification', value: 'terrestrial' })
);

The EO Store also supports findRecord and findRecords methods. These methods are async and call query internally:

// find all records of a type
let planets = await store.findRecords('planet');

// find a specific record by type and id
let planet = await store.findRecord('planet', 'abc123');

These methods are also available on the EO Cache, but are synchronous:

// find all records of a type
let planets = store.cache.findRecords('planet');

// find a specific record by type and id
let planet = store.cache.findRecord('planet', 'abc123');

Updating Data

There are two primary approaches to update data in EO:

  • Directly via async methods on the main Store. Direct updates flow immediately into Orbit's request flow, where they can trigger side effects, such as remote server requests.

  • In an isolated "forked" Store, usually via sync methods on its associated Cache and/or Model instances. These changes remain in this fork until they are merged back to a base store.

Direct Updates to the Store

The Store exposes several async methods to update data:

  • addRecord - adds a single record.
  • updateRecord - updates the fields of a single record.
  • updateRecordFields - for updating the fields of a single record, with a first argument that provides the identity of the record.
  • removeRecord - removes a single record.
  • update - the most flexible and powerful method, which can perform one or more operations in a single request.

Here are some examples of each:

// add a new record (returned as a Model instance)
let planet = await store.addRecord({ type: 'planet', id: '1', name: 'Earth' });
console.log(planet.name); // Earth

// update one or more fields of the record
await store.updateRecord({ type: 'planet', id: '1', name: 'Mother Earth' });
console.log(planet.name); // Mother Earth

// remove the record
await store.removeRecord({ type: 'planet', id: '1' });
// or alternatively: await store.removeRecord(planet);

// add more planets in a single `Transform`
let [mars, venus] = await store.update((t) => [
  t.addRecord({ type: 'planet', name: 'Mars' }),
  t.addRecord({ type: 'planet', name: 'Venus' })
]);

Updates via Forking / Merging

EO stores can be forked and merged, just as described in the Orbit guides.

Once you have forked a store, you can proceed to make synchronous changes to the fork's associated Cache and/or Model instances. These changes will be tracked and can then be merged back to the base store.

Here's an example:

  // (async) start by adding two planets and a moon to the store
  await store.update(t => [
    t.addRecord(earth),
    t.addRecord(venus),
    t.addRecord(theMoon)
  ]);

  // (async) query the planets in the store
  let planets = await store.query(q => q.findRecords('planet').sort('name')));
  console.log('original planets', planets);

  // (sync) fork the store
  forkedStore = store.fork();
  let forkedCache = forkedStore.cache;

  // (sync) add a planet and moons to the fork's cache
  forkedCache.update(t => [
    t.addRecord(jupiter),
    t.addRecord(io),
    t.addRecord(europa)
  ]);

  // (sync) query the planets in the forked cache
  planets = forkedCache.query(q => q.findRecords('planet').sort('name')));
  console.log('planets in fork', planets);

  // (async) merge the forked store back into the original store
  await store.merge(forkedStore);

  // (async) query the planets in the original store
  planets = await store.query(q => q.findRecords('planet').sort('name')));
  console.log('merged planets', planets);

Some notes about forking / merging:

  • Once a store has been forked, the original and forked stores’ data can diverge independently.

  • Merging a fork will coalesce any changes made to the forked cache into a single new transform, and then update the original store.

  • A store fork can simply be abandoned without cost. Just remember to free any references to the JS objects themselves.

Important - One additional concern to be aware of is that EO will generate new records for each store. Care should be taken to not mix records between stores, since the underlying data in each store can diverge. If you need to access a record in a store's fork, just query the forked store or cache for that record.

Sync Updates via the Cache

The Cache exposes sync versions of the Store's async update methods:

  • addRecord - for adding a single record.
  • updateRecord - for updating the fields of a single record.
  • removeRecord - for removing a single record.
  • update - the most flexible and powerful method, which can perform one or more operations in a single request.

By default, only forked caches are able to be updated directly. This provides protection against data loss, since changes to caches do not participate in Orbit's data flows. An exception is made for forks because the changes are tracked and applied back to stores via merge.

If you want to override these protections and update a non-forked cache, you can set cache.allowUpdates = true, but know that those updates won't leave the cache.

Sync Updates via Model instances

Each Model exposes all of its fields, including attributes and relationships, as properties that stay updated.

Attributes and has-one relationships are also directly editable. For instance:

let jupiter = forkedCache.findRecord('planet', 'jupiter');
let sun = forkedCache.findRecord('star', 'theSun');

console.log(jupiter.name); // 'Jupiter'

// update attribute
jupiter.name = 'Jupiter!';
console.log(jupiter.name); // 'Jupiter!'

// update has-one relationship
jupiter.sun = theSun;

In order to not conflict with user-defined fields, all standard methods on Model are prefixed with a $. The following synchronous methods are available:

  • $replaceAttribute
  • $replaceRelatedRecord
  • $replaceRelatedRecords
  • $addToRelatedRecords
  • $removeFromRelatedRecords
  • $update
  • $remove
let jupiter = forkedCache.findRecord('planet', 'jupiter');
let io = forkedCache.findRecord('moon', 'io');
let europa = forkedCache.findRecord('moon', 'europa');
let sun = forkedCache.findRecord('star', 'theSun');

jupiter.$replaceAttribute('name', 'JUPITER!');
jupiter.$addToRelatedRecords('moons', io);
jupiter.$removeFromRelatedRecords('moons', europa);
jupiter.$replaceRelatedRecord('sun', sun);

console.log(jupiter.name); // 'JUPITER!'
console.log(jupiter.moons.includes(io)); // true
console.log(jupiter.moons.includes(europa)); // false
console.log(jupiter.sun.id); // 'theSun'

Behind the scenes, these changes each result in a call to forkedCache.update. Of course, this method could also be called directly instead of issuing updates through the model:

forkedCache.update((t) => [
  t.replaceAttribute(jupiter, 'name', 'JUPITER!');
  t.addToRelatedRecords(jupiter, 'moons', io);
  t.removeFromRelatedRecords(jupiter, 'moons', europa);
  t.replaceRelatedRecord(jupiter, 'sun', sun);
]);

Adding a "backup" source

The store's contents exist only in memory and will be cleared every time your app restarts. Let's try adding another source and use the dataCoordinator service to keep it in sync with the store.

We can use the data-source generator to create a backup source:

ember g data-source backup --from=@orbit/indexeddb

This will generate a source factory in app/data-sources/backup.js:

import SourceClass from '@orbit/indexeddb';
import { applyStandardSourceInjections } from 'ember-orbit';

export default {
  create(injections = {}) {
    applyStandardSourceInjections(injections);
    injections.name = 'backup';
    return new SourceClass(injections);
  }
};

Note that injections should include both a Schema and a KeyMap, which are injected by default for every EO application. We're also adding a name to uniquely identify the source within the coordinator. You could optionally specify a namespace to be used to name the IndexedDB database.

Every source that's defined in app/data-sources will be discovered automatically by EO and added to the dataCoordinator service.

Next let's define some strategies to synchronize data between sources.

Defining coordination strategies

There are four different types of coordination strategies that can be generated by default using the standard data-strategy generator:

  • request
  • sync
  • event-logging
  • log-truncation

Let's define a sync strategy to backup changes made to the store into our new backup source.

ember g data-strategy store-backup-sync --type=sync

This should create a SyncStrategy factory in app/data-strategies/store-backup-sync.js as follows:

import { SyncStrategy } from '@orbit/coordinator';

export default {
  create() {
    return new SyncStrategy({
      name: 'store-backup-sync',

      /**
       * The name of the source which will have its `transform` event observed.
       */
      source: 'store',

      /**
       * The name of the source which will be acted upon.
       *
       * When the source receives the `transform` event, the `sync` method
       * will be invoked on the target.
       */
      target: 'backup',

      /**
       * A handler for any errors thrown as a result of invoking `sync` on the
       * target.
       */
      // catch(e) {},

      /**
       * A filter function that returns `true` if `sync` should be performed.
       *
       * `filter` will be invoked in the context of this strategy (and thus will
       * have access to both `this.source` and `this.target`).
       */
      // filter(...args) {};

      /**
       * Should resolution of the target's `sync` block the completion of the
       * source's `transform`?
       *
       * Can be specified as a boolean or a function which which will be
       * invoked in the context of this strategy (and thus will have access to
       * both `this.source` and `this.target`).
       */
      blocking: true
    });
  }
};

You should also consider adding an event logging strategy to log events emitted from your sources to the browser console:

ember g data-strategy event-logging

Sources have another kind of log as well: a transform log, which tracks transforms that are applied. A log truncation strategy will keep the size of transform logs in check. It observes the sources associated with the strategy and truncates their transform logs when a common transform has been applied to them all. Let's add a log truncation strategy as well:

ember g data-strategy log-truncation

Activating the coordinator

Next we'll need to activate our coordinator as part of our app's boot process. The coordinator requires an explicit activation step because the process is async and we may want to allow developers to do work beforehand.

In our case, we want to restore our store from the backup source before we enable the coordinator. Let's do this in our application route's beforeModel hook (in app/routes/application.js):

import Route from '@ember/routing/route';
import { inject as service } from '@ember/service';

export default class ApplicationRoute extends Route {
  @service dataCoordinator;
  @service store;

  async beforeModel() {
    // Populate the store from backup prior to activating the coordinator
    const backup = this.dataCoordinator.getSource('backup');
    const records = await backup.query((q) => q.findRecords());
    await this.store.sync((t) => records.map((r) => t.addRecord(r)));

    await this.dataCoordinator.activate();
  }
}

This code first pulls all the records from backup and then syncs them with the main store before activating the coordinator. In this way, the coordination strategy that backs up the store won't be enabled until after the restore is complete.

Defining a data bucket

Data buckets are used by sources and key maps to load and persist state. You will probably want to use a bucket if you plan to support any offline or optimistic UX.

To create a new bucket, run the generator:

ember g data-bucket main

By default this will create a new bucket factory based on @orbit/indexeddb-bucket. It will also create an initializer that injects this bucket into all your sources and key maps.

Customizing EO

The types, collections, and services used by EO can all be customized for your application via settings under the orbit key in config/environment:

module.exports = function (environment) {
  let ENV = {
    // ... other settings here

    // Default Orbit settings (any of which can be overridden)
    orbit: {
      schemaVersion: undefined,
      types: {
        bucket: 'data-bucket',
        model: 'data-model',
        source: 'data-source',
        strategy: 'data-strategy'
      },
      collections: {
        buckets: 'data-buckets',
        models: 'data-models',
        sources: 'data-sources',
        strategies: 'data-strategies'
      },
      services: {
        store: 'store',
        bucket: 'data-bucket',
        coordinator: 'data-coordinator',
        schema: 'data-schema',
        keyMap: 'data-key-map',
        normalizer: 'data-normalizer',
        validator: 'data-validator'
      },
      skipStoreService: false,
      skipBucketService: false,
      skipCoordinatorService: false,
      skipSchemaService: false,
      skipKeyMapService: false,
      skipNormalizerService: false,
      skipValidatorService: false
    }
  };

  return ENV;
};

Note that schemaVersion should be set if you're using any Orbit sources, such as IndexedDBSource, that track schema version. By default, Orbit's schema version will start at 1. This value should be bumped to a a higher number with each significant change that requires a schema migration. Migrations themselves must be handled in each individual source.

Conditionally include strategies and sources

Sources and strategies may be conditionally included in your app's coordinator by customizing the default export of the source / strategy factory. A valid factory is an object with the interface { create: () => {} }. If a valid factory is not the default export for your module, it will be ignored.

For example, the following strategy will be conditionally included for all non-production builds:

// app/data-strategies/event-logging.js

import { EventLoggingStrategy } from '@orbit/coordinator';
import config from 'example/config/environment';

const factory = {
  create() {
    return new EventLoggingStrategy();
  }
};

// Conditionally include this strategy
export default config.environment !== 'production' ? factory : null;

Customizing validators

Like Orbit itself, EO enables validators by default in all sources. EO provides the same set of validators to all sources by building a single data-validator service that is injected into all sources.

Validators are useful to ensure that your data matches its type expectations and that operations and query expressions are well formed. Of course, they also add some extra code and processing, which you may want to eliminate (or perhaps only for production environments). You can disable validators across all sources by setting Orbit's skipValidatorService environment flag to false in config/environment, as described above.

If you want to use validators but extend them to include custom validators, you can override the standard validator service by generating your own data-validator service that passes custom arguments to buildRecordValidatorFor.

For instance, in order to provide a custom validator for an address type:

// app/services/data-validator.js

import { buildRecordValidatorFor } from '@orbit/records';

const validators = {
  address: (input) => {
    if (typeof input?.country !== 'string') {
      return [
        {
          validator: 'address',
          validation: 'country',
          description: 'is not a string',
          ref: input,
        },
      ];
    }
  },
};

export default {
  create() {
    return buildRecordValidatorFor({ validators });
  },
};

This custom validator service will be injected into all your orbit sources via applyStandardSourceInjections, as described above.

Contributing to EO

Installation

  • git clone https://github.com/orbitjs/ember-orbit.git
  • cd ember-orbit
  • yarn install

Running Tests

  • yarn test

Acknowledgments

EO owes a great deal to Ember Data, which has influenced the design of many of EO's interfaces. Many thanks to the Ember Data Core Team, including Yehuda Katz, Tom Dale, and Igor Terzic, for their work.

It is hoped that, by tracking Ember Data's features and interfaces where possible, EO will also be able to contribute back to Ember Data.

License

Copyright 2014-2021 Cerebris Corporation. MIT License (see LICENSE for details).

ember-orbit's People

Contributors

bmoeskau avatar captain-enjoyable avatar cibernox avatar dependabot[bot] avatar derekwsgray avatar devotox avatar dgeb avatar gnarf avatar jakecraige avatar lukemelia avatar mattmcmanus avatar mitchlloyd avatar opsb avatar pangratz avatar pixelhandler avatar prolificlab avatar robbiethewagner avatar rwjblue avatar simonihmig avatar tchak avatar tute avatar walter avatar xiphiasuvella 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

ember-orbit's Issues

Request: Support for local (non syncable) fields

In my current setup (main store: local storage, secondary: json, transformation: json => main) I need some fields in the schema which should be only available inside the main store, but not in the secondary store. Within a normal setup, every call to the api would overwrite these fields in the main store to null because these fields do not exist in the json.

My current (hacky) solution overwrites the deserializeRecord method to get this behaviour:

App.Serializer = OC.JSONAPISerializer.extend({
  deserializeRecord: function(type, id, data) {
    if (id) {
      data[this.schema.models[type].primaryKey.name] = id;
    }
    if(type === 'item') {
      var oldItem = OrbitLocalSource.retrieve(type, data.id); //main store
      if (oldItem !== undefined) {
        data.nonSyncableField = oldItem.get('nonSyncableField');
      }
  }
  var normalized = this.schema.normalize(type, data);
  return normalized;
});

Instead, cool would be to have something like this:

App.Item = EO.Model.extend({
  title: EO.attr('string', {transformable: false})
})

What do you think?

P.S.: I am working on a fairly complex project and orbit just works, thanks 👍

How to run tests?

When I follow the instructions I get the error below. I'm not too familiar with how AMD and karma interact so I haven't been able to figure it out.

$ git clone https://github.com/orbitjs/ember-orbit.git
$ cd ember-orbit
$ npm install
$ bower install
$ grunt test:ci

Running "karma:ci" (karma) task
INFO [karma]: Karma v0.10.10 server started at http://localhost:9876/
INFO [launcher]: Starting browser PhantomJS
WARN [watcher]: Pattern "/Users/opsb/Projects/ember-orbit/tmp/public/test/vendor/loader.js" does not match any file.
INFO [PhantomJS 1.9.7 (Mac OS X)]: Connected on socket HDtCHMqpWbc-ZUHlm1e8
PhantomJS 1.9.7 (Mac OS X) ERROR
ReferenceError: Can't find variable: define
at /Users/opsb/Projects/ember-orbit/tmp/public/test/vendor/orbit.amd.js:44
...

Unhandled change for replace operation in RecordArrayManager

See RecordArrayManager#_processChange
https://github.com/orbitjs/ember-orbit/blob/master/lib/ember-orbit/record-array-manager.js#L66-L77

This line logs out an unhandled change...

console.log('!!!! unhandled change', path.length, operation);

Like so:

 !!!! unhandled change 5 Object {op: "replace", path: Array[5], value: "488c793d-20cb-4f9d-aa2f-7b77b82f2e24"}
  op: "replace" 
  path: Array[5]
    0: "author"
    1: "5c9b62ec-1569-448b-912a-97e6d62f493e"
    2: "__rel"
    3: "posts"
    4: "488c793d-20cb-4f9d-aa2f-7b77b82f2e24"
  value: "488c793d-20cb-4f9d-aa2f-7b77b82f2e24"

A "replace" operation with path.length of 5 is not handled however the RecordArrayManager#_processChange does handle add and remove.

The use case I have is:
Given a post with a linked author (and vice-versa) I called author.removeLink('posts', post) to remove the linked post from the author resource then called post.remove() to destroy the post resource.

I'll have to dig in to see why the replace operation was fired, it seems like the operation is odd, looks like it wants to replace the existing value for the author's linked post id.

How do transform-connector, transformations, actions and operations fit together

I've got a pretty good mental model of how everything works in orbit.js and ember-orbit now. One area which is very hazy though is what's happening with the transformation pipeline, in particular how the transform-connector mediates the processing flow in transformable. @dgeb could you spare a few minutes in IRC at some point to answer a few questions? I'd be very happy to write it up in the wiki.

Replace hasMany relationship

@dgeb I've got a rough sketch of the firebase source working now so I'm now looking at ordered arrays again. It doesn't look like ember-orbit supports replacing hasMany relationships yet. Do I take it this needs to be added to record-array-manager? I'm looking at adding a _linkWasReplaced to go with _linkWasAdded and _linkWasRemoved.

retrieveLink trying to retrieve link for links field when saving record

Getting a strange issue when trying to save a new record. The POST request is getting sent through fine but when ember-orbit tries to process the returned json it's throwing an error in didTransform. Upon further investigation it appears that it's trying to call Store._retrieveLink on the links field. To be honest this could just be that I'm returning something invalid in my POST response but if I take the links field out it ends up trying to delete the relationship.

I can't post an exact copy of my POST json because of data sensitivity but I the basic structure is like this:

{
    "data":{
        "id":"some-uuid-string",
        "propertyA": "a",
        "propertyB": "b",
        "links": {
            "relationship1":"some-other-uuid-string",
            "relationship2": [
                 "uuid-1",
                 "uuid-2"
             ]
        }
    }
    "linked": {
        "dataType1": [array of loaded data types],
        "dataType2": [array of loaded data types]
    }
}

Any ideas or more helpful information I can give you?

Data-binding and added related entries in compound json documents

Currently I am trying to realize the following use case: I am on the detail page of a resource (e.g. cars/1) and I am trying in the background to check for updates on the rest server with restSource.find('car', '1'); to share this data with my localstorage main store. This works great. If a field of the car changes, it is directly refreshed on the page through data-binding.

However, there is one issue: Lets say the car has a hasMany relationship with tires. If I add a tire on the server, synchrionization will add the new tire object to my local storage source. Unfortunately, data-binding does not seem to work here. The template won't recognize that a new tire has been added. I have also tried to observe the tires field of the model in the controller, but no luck: Its not fired. Refreshing the page solves the issue, all recently added tires are then available in the template.

Any idea why this fails? I will try to come up with a failing test case for this issue.

Reduce number of PUT requests when setting properties

Using Ember-Orbit with a JSONAPISource, as soon as I set a property on a model it sends a PUT request to update that property on the server. This isn't so bad and I can see that potentially being intended behaviour, although I would prefer it to work more like Ember-Data where changes aren't commited to the source until I call save, but what I would expect to happen is that if I call setProperties with a list of properties to update it would set a single PUT request updating all of them, however it appears it still sends one request per property.

I'm not sure if there is some logic behind why it is done this way but it seems kind of inefficient to me and seeing as I'm building this as part of a mobile app, a little too bandwidth heavy for my tastes.

Couldn't see any obvious way of overriding this but if someone can point me in the right direction, or suggest a better way of dealing with this I'd be most grateful.

Cheers

Model not using auto generated id as primary keys

When I do not define and id as a key in my model, e.g. id: key({primaryKey: true, defaultValue: uuid}), Schema#registerModel throws an error, as primaryKey and defaultValue are not defined.

Per docs... "If no primary key is defined on a model, a single primary key named id will be automatically generated. This key's defaultValue will be set to the v4 UUID generator used within Orbit."

I updated to orbit.js 0.5.2 and ember-orbit 0.4.0 but ran into an issue with model id (primary keys), my bower file

After pixelhandler-blog/routes/application (Ember.Route#model)

model: function () {
  return this.store.find('post');
},

Error thrown in Schema#registerModel as modelSchema.primaryKey and modelSchema.primaryKey.defaultValue is not defined.

// ensure every model has a valid primary key
if (!modelSchema.primaryKey || typeof modelSchema.primaryKey.defaultValue !== 'function') {
    throw new OperationNotAllowed('Model schema ID defaultValue must be a function');
}

Branch on my blog app: https://github.com/pixelhandler/blog/compare/ember-cli-upgrade

Models:

bower packages

Running bower install orbit as per the README installs the 'orbit' zurb component not orbit.js

How to commit changes on form submit?

Right now, when I edit a model, I see on every change (including key presses), the following operations being enqueued:

screen shot 2014-07-14 at 11 10 42 am

Right now I'm synchronizing memory with localStorage, so this is kind of free. But when I connect the JSON endpoint it will fire too many HTTP requests. How can I submit all the changes when the editing finishes (let's say, on form submit)?

Proposal: Generate test data with factories

I just finished upgrading ember-data-factory-guy to spit out JSONAPI documents, and it might be useful for ember-orbit applications. If anyone is interested, I could help to extract the core parts of the addon to make a new addon for ember-orbit.

Creating an object graph

It's currently possible to create an object graph using syntax like:

store.create('user', {
  organisation: organisationAttributes
});

but this isn't mentioned in the docs anywhere. Is this syntax going to be supported ongoing?

'FindLinked' not setting properties with JSON API source

I am having an issue where records that I'm requesting are coming back with null properties, though the server is returning values for those properties. I'm a little confused as to what's going in, but here is my digging (bear with me):

To cut to the chase, I think the problem has something to do with this line here https://github.com/orbitjs/ember-orbit/blob/master/lib/ember-orbit/store.js#L196

I am using the orbit common JSON API source.

First, I call reload on a hasMany field to fetch the data for each of the related items. This calls findLinked which fires off a request to the JSON API source. The request then calls the orbit common _findLinked method which calls the JSON API source's _find method. This calls _findMany, which fires off an ajax request.

Now at this point, orbit's settleTransforms gets called before the ajax request returns a response, which fires off some didTransform events for the returned records, eventually resulting in a call to RecordArrayManager.recordDidChange. Seems all good.

OK so now we're back in the .then callback from the ajax request (which is inside the JSON API source's _findMany), which deserializes the records and makes another call to settleTransforms and orbit does some more transformation stuff in the background, and returns THE CORRECT array of records from the server.

Now for the good part. Back in findLinked in the callback from orbit source https://github.com/orbitjs/ember-orbit/blob/master/lib/ember-orbit/store.js#L196, the variable data holds the CORRECT data as it should. Then, in the call to _lookupFromData we get to here https://github.com/orbitjs/ember-orbit/blob/master/lib/ember-orbit/store.js#L336 return _lookupRecords(type, id); which takes us to here 'https://github.com/orbitjs/ember-orbit/blob/master/lib/ember-orbit/store.js#L306'. At this point record does not have any of the info returned from the server. On top of that record.__ember_meta__.cache has the record's properties but set to null (I think this may be because these records are loaded asynchronously, so the first time they were requested and cached, the values were null)! This is the value that gets returned all the way back to my controller.

I'm not sure what the solution is. Is it as simple as something like merging the data object into the values returned from _lookupRecords here https://github.com/orbitjs/ember-orbit/blob/master/lib/ember-orbit/store.js#L336? What is confusing me is that orbit seems to rely heavily on the records' __ember_meta__.cache property, which I don't fully understand.

@dgeb Happy to provide more info.

How do I remove items? (and related issues)

  1. In the route I've got: this.store.orbitSource.find('model');.
  2. In the view I have a link to a controller action: {{action "delete" this}}. this is an instance of the found models.
  3. In the controller I receive an object with no methods get, remove, etc, and not even calling remove directly over the store deletes it.

How can I remove them? Thank you!

hasMany relationship returns non-array response with 1 linked record

When you have a has many with one linked record, the request that's generated looks like a lookup of only one record and thus is incorrect

URL with one record:

http://localhost:3000/todos/1deb7b43-689b-4dd4-b152-6d19ac629e18

URL with multiple records

http://localhost:3000/todos/d5f59fa5-5e50-411f-a3f1-bf9699891120,2e4ced5d-64d7-4e7a-a553-3ec4560ebbaf

The response differs in that the second one returns an array while the first doesn't. I'm not sure if this is something internal to ember-orbit, JR, or orbit.js itself. My assumption would be that the server has no way to tell that it wants an array, so the js must coalesce it into an array before responding. Seems simple enough to do, I just want to verify that's the correct solution

Lazy loading relationships

At the moment relationships only call store.retrieve to load records. But there's no guarantee that the record has actually been loaded into the store. I would expect store.findLinked to be called both when a relationship is first loaded and then when there are changes. What's the expected data flow here?

I'm considering a couple of things:

  1. Use calls to objectAtContent to lazily call store.findLinked - once the relationship has been loaded a flag would be set on the relationship, loaded = true.

  2. Add an autoReload option e.g.

export default EO.Model.extend({
    comments: EO.hasMany('comment', {inverse: 'user', autoReload: true}),
});

which would instruct the relationship to call reload() (from link-proxy-mixin) whenever the array was changed.

@dgeb I'll be getting on with this today so let me know if you think this is the wrong direction.

Modify 'find' promise to resolve to an object instead of an array when an id is specified

For setting up ObjectControllers, ember wants an object not an Array. i.e., in a route's model hook,

model: function(params) {
    return this.store.find('post',1);
}

should return a promise that resolves to a single object. Right now an array with one element is returned.
Something like changing https://github.com/orbitjs/ember-orbit/blob/master/lib/ember-orbit/store.js#L114-L116

to

var promise = this.orbitSource.find(type, id).then(function(data) {
    var collection = _this._lookupFromData(type, data);
    if (id && !(isArray(id) || isObject(id)) { //TODO: verify this is an appropriate check based on the orbit query language
        return collection.length > 0 ? collection[0] : null; //TODO: verify that null is the appropriate value, or maybe an error should be thrown?
    }
    return collection;
});

Integration with ember-cli

For integrating into an ember-cli project that used data, I'm doing:

  1. Remove ember-data altogether (I suspect it was the source of the The initializer 'injectStore' has already been registered error). In my case it was tied to the models, DS.RESTAdapter, package and bower.json, and a custom app/transforms/array.js.

  2. Add to bower.json (got this snippet from https://github.com/opsb/ember-orbit-todos/blob/master/bower.json):

    "orbit.js": "~0.2",
    "ember-orbit-development": "https://lytbulb-vendor.s3.amazonaws.com/ember-orbit/347f1d3/ember-orbit.js",
    "ember-orbit-production": "https://lytbulb-vendor.s3.amazonaws.com/ember-orbit/347f1d3/ember-orbit.min.js"
    

    This is related with #7, about having an npm package for ember-orbit (that could require orbit.js itself).

  3. Add app/initializers/orbit-store.js, similar to https://github.com/orbitjs/ember-orbit#initialization. Now stuck in there, with errors like ReferenceError: Orbit is not defined.

Are there blog posts or guidelines on how to integrate it into an existing project? If not, we may start off of this issue!

Thanks for your work.

Loading orbit modules to ember-cli files

Thanks @dgeb for your work and this commit: 41b4836#diff-04c6e90faac2675aa89e2176d2eec7d8.

I followed it's instructions and it seems my app is loading the dependencies it needs. Though following the same README the initializer doesn't run. First, it's first line should read something like export default {, following ember-cli's modules approach.

And then Orbit and EO are not defined, how should I include them? Doing something like import Orbit from 'orbit' didn't work.

Thanks again for your work!

OrderedSet.prototype.remove Deprecated in Ember.js use OrderedSet.prototype.delete

In the console I get a warning: "Calling OrderedSet.prototype.remove has been deprecated, please use OrderedSet.prototype.delete instead." See method call at https://github.com/orbitjs/ember-orbit/blob/master/lib/ember-orbit/record-arrays/record-array.js#L71

Code to review/update :

_recordRemoved: function(record) {
  this._recordArraysForRecord(record).remove(this);
},

I'm using:

  • orbit.js at commit: 6c3350c 2014-10-21 | Merge pull request #66 from leejt489/master
  • ember-orbit at commit: 58d858a 2014-10-03 | Merge pull request #37 from jakecraige/patch-1

Trace:

DEBUG: ------------------------------- 
DEBUG: Ember      : 1.9.0-beta.1+canary.12e2ea41
DEBUG: Handlebars : 1.3.0
DEBUG: jQuery     : 1.11.1
DEBUG: ------------------------------- 
...
...
DEPRECATION: Calling `OrderedSet.prototype.remove` has been deprecated, please use `OrderedSet.prototype.delete` instead.
        at OrderedSet.remove
        at Ember.ArrayProxy.extend._recordRemoved
        at Ember.ArrayProxy.extend.removeObject
        at Ember.Object.extend.updateRecordArray

Custom routes

I have a model called Category. Of course just appending 's' for the rest call does not work here (its categories!). The schema allows to supply a pluralize function where I could handle that. Unfortunately, I have no idea where to set this property...right now its only possible to set the property during initlization, but this is done in the background. Where/how can I set the pluralize function?

Another question: I have some routes that don't follow the typical route system of host/resource but host/someOtherResource/id/resource. Is it somehow possible to customize the model to adjust the lookup address?

edit sorry, i solved the first part, its just as simple as

Schema = EO.Schema.extend({
  idField: 'id',
  pluralize: function(word) {
    return 'abc'
  }
});

Setup issues using global builds

I'm trying to setup orbit using the global builds; however, after including the javascript files (ember+ orbit files) the dependant objects on EO are undefined. The EO object itself can be resolved. An inspection of the object just shows its including the Source and Store objects beneath default.

For instance:
EO.Source is undefined.
EO.Store is undefined.

<script src="/assets/handlebars.js?body=1"></script>
<script src="/assets/ember.js?body=1"></script>
<script src="/assets/orbit.js?body=1"></script>
<script src="/assets/orbit-common.js?body=1"></script>
<script src="/assets/orbit-common-local-storage.js?body=1"></script>
<script src="/assets/ember-orbit.js?body=1"></script>

Any ideas?

Transformations don't work with array attributes

Not sure if it's a use case Orbit.js will want to support. Problem starts in

if (path.length === 3) {
// attribute changed
record.propertyDidChange(path[2]);
} else if (path.length === 4) {
// hasOne link changed
var key = path[3];
var link = this.retrieveLink(path[0], path[1], key);
record.set(key, link);
}
, seemingly because the path.length is bigger than intended.

How can we define a custom transforms to support array attributes?

Thank you in advance!

Set links when creating new model

I have a two models Star and Moon :

 Planet = Model.extend({
    name: attr('string'),
    moons: hasMany('moon', {inverse: 'planet'})
  });

  Moon = Model.extend({
    name: attr('string'),
    planet: hasOne('planet', {inverse: 'moons'})
  });

I would like to be able to link a new moon to an existing planet when I create the model like this :

store.add("planet", {name: "Earth"}).then(
  function(earth) {
    store.add("moon", {name: "moon",planet:earth}).then(
      function(moon) {
        console.log(moon.get("planet.name")); // => Earth
      });
  });

But it doesn't work because earth in {name: "moon",planet:earth} is not recognize as a link. I think we have to modify the schema.normalize function to change this but I don't know how to do this properly because it is directly using orbits.js schema.normalize function underneath.

What should I do about this ?

Relationships update and JSON API

I have memory and localStorage sources connected successfully, and hasOne-hasMany relations defined with their inverses.

When I add JSON API source though, it tries to update the relationships unsuccessfully. For example, while creating a new object I send the id of the parent object, API stores it as it should, and sends back the created JSON. Orbit sees the new relationship with the parent, and tries to update both objects __rel attributes, firing unnecessary API calls, that the server doesn't need nor understand.

These updates are also rejecting promises in the Link methods of ember-orbit, making other promises pending ("waiting for parent"), effectively stopping any other activity until I reload/reset the app's state.

Another example is while deleting a child object. To make deletion work, I have to undo the inverse call in the parent object:

 export default EO.Model.extend({
-  plots: EO.hasMany('plot', { inverse: 'farm' }),
+  plots: EO.hasMany('plot'),

Then DELETE works. But it feels funky.

How should I deal with this problem? As always, thank you very much for your work.

ES6 modules

The standard chosen by ember-cli is ES6 modules, but now building the app with ember-cli defaults it raises many warnings:

client/initializers/ember-orbit.js: line 3, col 25, 'EO' is not defined.
client/initializers/ember-orbit.js: line 4, col 21, 'OC' is not defined.
client/initializers/ember-orbit.js: line 12, col 5, 'Orbit' is not defined.
client/initializers/ember-orbit.js: line 14, col 41, 'EO' is not defined.

4 errors

client/models/assessment.js: line 1, col 16, 'EO' is not defined.
client/models/assessment.js: line 2, col 9, 'EO' is not defined.
client/models/assessment.js: line 3, col 12, 'EO' is not defined.
client/models/assessment.js: line 5, col 21, 'EO' is not defined.
...

Does ember-orbit export ES6 modules, or globals? If ES6 modules, how to include them in the my sources? If not, how could I fix it?

Thanks for your time!

Using global dist file: "orbit_common/schema" module not found

I'm not using EAK or ember-cli, so no common loader.js dependency is available. I decided to try the global dist files (instead of the AMD dist files).

I was not able to use use ember-orbit, the browser threw an error on the orbit_common/schema dependency in the global dist of ember-orbit.

Steps to reproduce

  • Use orbit source from bower dist, 0.2.0
  • Build ember-orbit dist files grunt package 0.1.0

Setup an ember app using 'global' dist package files for orbit.js and ember-orbit (e.g. script elements with src attributes for global dist files, not AMD dist files, in the index.html file).

Result

Error in the browser's console: Error: Module: 'orbit_common/schema' not found.

Attempted changes

Using Ember computed properties with linked records

My question is how can we use the .property() tag to trigger updating a computed property based on the state of promises with orbit. I was previously able to implement with ember-data, so I think it is an orbit specific issue.

For an example, my model has a hasMany relationship with a "display" model that contains language specific display information. This "display" model has in turn has a hasOne relationship with a language model. I want to declare a currentDisplay property on my controller that returns a single display based on another currentLanguage property, but to keep things simple, let's just filter on a static value "en"

currentDisplay: function() {
    var displays = this.get('displays');
    if (!displays.content) displays.reload();
    var filtered = displays.filter(function (display) {
        var language = display.get('language');
        if (!language.content) {
            language.reload();
            return false;
        } else {
            return language.get('name') === "en";
    });
    return filtered;
}.property('[email protected]')

Using .property('[email protected]') triggered the function to be called when the promises were resolved using ember-data, but the function is only getting called once on the initial loading of the controller using orbit. I have also tried .property('[email protected]'), .property('[email protected]'), .property('displays.@each._links.language.content'), and .property('displays.content.@each.)links._.language.content') with no success. I think the syntax in the example above is the most intuitive.

The way I have been using this is that displays is loaded into the cache along with the controller's model in the initial call to a JSON API during route processing, but the language data is not.

Also, is this the proper way to use reload()?

Boolean attr with LocalStorageSource not being stored

I am having trouble getting the boolean attribute to save to the LocalStorageSource in my app. Other attributes, hasOne and string are working with no problem. I have a bound checkbox but it never seems to save the value.

import EO from 'ember-orbit';

export default EO.Model.extend({
  // ...
  hideStaffDropdown:  EO.attr('boolean', { defaultValue: false }),
});
{{input checked=user.hideStaffDropdown type="checkbox"}}
> $E.user.toString()
< "<booker-extension@model:user::ember758>"
> $E.user.get("hideStaffDropdown")
< null

When I log the record I get this <booker-extension@model:user::ember758> so it seems like I'm working with the proper record. Also when I set/get values on it the checkbox on screen updates as expected and the value on the user model updated. But after a refresh the value is then set to null. All the other properties on this page are updating properly

Can you provide any insight into this?
Thanks.

Configuration for snake_cased irregular noun

My api currently has an endpoint for stamina_activities (god, why did I pick this disastrous name), and I fail miserably every time I try to get this endpoint to play nicely with third-party libraries.

I have an ember-cli model at stamina-activity.js, which is used by the store as store.find('staminaActivity') and in a has many relationship like so: model.get('staminaActivities'), defined by staminaActivities: EO.hasMany('staminaActivity').

How do I make Orbit understand this mess? I've added the following pluralization rules to the schema:

var EO_Schema = EO.Schema.extend({
  pluralize: function(word) {
    if (word === 'staminaActivity') return 'staminaActivities';
    return word + 's';
  },

  singularize: function(word) {
    if (word === 'staminaActivities') return 'staminaActivity';
    return word.substr(0, word.length - 1);
  }
});

and i'm using the snake -> camel defined in peeps-ember-orbit.

It works alright when fetching records directly using store.find('staminaActivity'), but when they are sideloaded, I get the following error:

TypeError: Cannot read property 'keys' of undefined
    at Class.extend.initDefaults
    at Class.extend.normalize
    at Serializer.extend.deserializeRecord [as _super]
    at JSONAPISerializer.default.extend.deserializeRecord

The model parameter in the normalize method at this time is stamina_activitie (without the s) when it should be staminaActivity. I can get rid of the error by adding some fancy logic in Schema#singularize (if (word === 'stamina_activities') return 'staminaActivity';), but the records are still not loaded and model.get('staminaActivities') returns an empty array. Calling reload() on it does nothing, and sends no request.

Question: Best place to wire TransformConnectors

I'm currently trying to create a bidirectional connector between two sources. As explained in the readme, it should be possible access the plain orbit object via orbitSource on an EO.Source object and wire it up. However, there seems to be no accessor for orbitSource if I see it correctly? Is that intended? Where would the best be to create the TransformConnectors? Overwriting the init method in one of them?

If there would be an accessor I would have done something like that:

LocalStorageSource = EO.Source.extend({
  orbitSourceClass: OC.LocalStorageSource,
  orbitSourceOptions: {
    namespace: "Xikolo"
  }
});

new Orbit.TransformConnector(LocalStorageSource.orbitSource, AnotherSource.orbitSource);

Testing / Development

I am wanting to contribute to this project so I cloned the repo and followed the instructions for building however its not possible to run the tests using current master.

Running "qunit:local" (qunit) task
Testing http://localhost:8010/test/ F
>> global failure
>> Message: Error: Could not find module orbit
>> http://localhost:8010/test/vendor/loader.js:17

Warning: 1/1 assertions failed (59ms) Use --force to continue.

Aborted due to warnings.

It seems bower_components is ignored for orbit.js.

So I manually copy the orbit.js files into vendor:

cp bower_components/orbit.js/dist/orbit*.amd.js vendor

And then run grunt test:ci which generates an exception followed by a series of failing tests:

Running "clean:build" (clean) task
Cleaning tmp...OK

Running "transpile:amd" (transpile) task

Running "concat:amd" (concat) task
File "tmp/built/ember-orbit.amd.js" created.

Running "jshint:lib" (jshint) task
>> 15 files lint free.

Running "jshint:tooling" (jshint) task
>> 13 files lint free.

Running "jshint:tests" (jshint) task
>> 6 files lint free.

Running "transpile:tests" (transpile) task

Running "concat:tests" (concat) task
File "tmp/public/test/tests/tests.amd.js" created.

Running "copy:tests" (copy) task
Copied 15 files

Running "connect:server" (connect) task
Started connect web server on http://0.0.0.0:8010

Running "qunit:local" (qunit) task
Testing http://localhost:8010/test/ ..FFFTypeError: Requested keys of a value that is not an object.
    at http://localhost:8010/test/lib/ember-orbit.amd.js:1274
    at http://localhost:8010/test/lib/ember-orbit.amd.js:255
    at http://localhost:8010/test/lib/ember-orbit.amd.js:785
    at http://localhost:8010/test/vendor/ember.js:1637
    at get (http://localhost:8010/test/vendor/ember.js:6314)

<... truncated ...>

Warning: 31/118 assertions failed (255ms) Use --force to continue.

Aborted due to warnings.

Any ideas ?

How to clear store between tests?

Currently I'm resetting the store by using

window.localStorage.setItem(config.modulePrefix, null);

is there any way to do it through the ember-orbit/orbit apis? (I couldn't find anything on the stores). If not is there are a plan for supporting this?

[Question] Should I be able to reference a relationship in the handlebars template and expect the link to be fetched?

Example:

I have a Post that has one Author, on a post page the author's name should be displayed.

Code snippets:

  • In Post (EO.Model) - author: hasOne('author', {inverse: 'posts'})
  • In Author (EO.Model) - posts: hasMany('post', {inverse: 'author'})
  • In templates/posts.hbs - {{author.name}}

Currently on my branch, use-relations, the post page is not showing the author name and I didn't see the author XHR request. I'm using the JSONAPISource and serializer.

I'm wondering if I need to do something in the post route's afterModel hook to get the author, e.g. model.get('author') and var author; store = this.store; store.then(function() { author = store.all('author').get('firstObject'); })

The blog app, branch: use-relations, can be run locally using a proxy to the prod api. [clone repo, npm install, bower install, ember server --proxy http://pixelhandler.com/api]

Post payload:

{
  "posts": {
    "body": "I may have missed a few segments...[removed]",
    "date": "2014-03-30T07:00:00.000Z",
    "excerpt": "I missed EmberCamp last year, but ...[removed]",
    "id": "341207e0-cfd9-4d3a-a5ab-d2268ab2e472",
    "links": {
      "author": "5c9b62ec-1569-448b-912a-97e6d62f493e"
    },
    "slug": "we-are-emberconf-2014",
    "title": "We are EmberConf 2014"
  }
}

Author payload

{
  "authors": {
    "email": "[email protected]",
    "id": "5c9b62ec-1569-448b-912a-97e6d62f493e",
    "links": {
      "posts": [
        "341207e0-cfd9-4d3a-a5ab-d2268ab2e472",
        "...[removed]..."
      ]
    },
    "name": "pixelhandler"
  }
}

Transform Connector

Hi,

Would it be possible to explain how to setup a transform connector with Ember-Orbit?

Would like to get MemorySource connecting to LocalStorage along with JSONAPISource.

For now the Memory > Local would be great.

I don't understand how to access the source instance from the Store wrapper, as well as when and where. Should this be setup in the initializer?

I'm using ember-cli btw.

Thanks so much for the amazing work, it's very exciting!

Proposal: Blocking/non-blocking store API

My app runs with a non-blocking connector to firebase. This means that UI updates are fast and the changes are saved in the background.

I've now got a single case where I need to block when adding a record i.e.

store.add('attachment', {filename: "report.xls"}).then(function(attachment){
    return uploadService.upload(file, attachment.id, userId);
});

The uploadService is calling through to a service running on a different host and uses the attachment.id and userId to authorize the upload. The problem is that because store is running with a non-blocking connector the attachment isn't actually necessarily stored in firebase when the upload service tries to authorize the upload.

What I'm thinking about is allowing the store to have two connectors, one for blocking, one for non-blocking. That way we could support an API like:

// default - configured as blocking or non-blocking when store is created
store.add('attachment', {filename: "report.xls"}).then(function(attachment){
    return uploadService.upload(file, attachment.id, userId);
});

// blocking
store.blocking.add('attachment', {filename: "report.xls"}).then(function(attachment){
    return uploadService.upload(file, attachment.id, userId);
});

// non-blocking
store.nonBlocking.add('attachment', {filename: "report.xls"}).then(function(attachment){
    return uploadService.upload(file, attachment.id, userId);
});

I have a workaround in place but I think this is a scenario that will crop up again and should be solved properly.

Alternatives:

Another option would be to just use two separate stores, this seems wasteful(for memory) and would make the sychronisation between the sources more complicated but would be easier to setup in the short term.

Model lifecycle events

I'm considering the equivalent of rails model callbacks in ember-orbit(after_create, before_save etc.). It looks like this could be implemented using orbit.js' notifications and events. @dgeb have you considered whether this is a desirable feature to have and does that seem like the most natural place to integrate them?

How can I generate UUIDs client side?

I have the following code in my orbit initializer:

var Schema = EO.Schema.extend({
  idField: 'id',
  remoteIdField: 'id',

  generateId: function() {
    var s4 = function () {
      return Math.floor((1 + Math.random()) * 0x10000)
                 .toString(16)
                 .substring(1);
    };
    return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
           s4() + '-' + s4() + s4() + s4();
  }
});
// ...
application.register('schema:main', Schema);

The goal is to generate UUIDs client side, and let the server know so. I can see how ifField is now preset from the first memoryStore I have defined, but orbit doesn't seem to see my own generateId UUID generator function. What is the proper way to extend the Schema to add such behavior?

Thank you!

Store.all doesn't load any records

store.all returns a live record-array but it's initially populated by source.retrieve which hit's the source's cache rather than querying for records. This means that to load a live array of all records you have to do:

store.find('type')
var populatedLiveArrayForType = store.all('type');

Is this intentional? Perhaps it would be better to initialize the record-array with source.find('type') and then use retrieve from that point on?

On a more general note, given orbit's style I expected all, filter and find to return live arrays as standard (seems more in line with orbit.js' style). I can see that there might be value in having some live and others not live though but I think the API should make this distinction clearer.

Proposal: model meta/configuration

Allow models to specify configuration for Sources e.g. I need to specify that the task model should be embedded inside the project model when stored in firebase.

// task.js
export default EO.Model.extend({
  configuration: {
    embeddedIn: 'project'
  },
  name: EO.attr('string'),
  project: EO.hasOne('project', {inverse: 'disciplines'}),
  tasks: EO.hasMany('task', {inverse: 'discipline'}),
  label: Ember.computed.alias("name")
});

Thoughts:

  1. Should we namespace configuration? e.g.
  configuration: {
    firebase: {
        embeddedIn: 'project'
    }
  },
  1. Should the configuration be made directly on the source rather than being kept in the model? (personal preference is to keep it on the model).

Polymorphism

Orbit doesn't seem to have support for polymorphism. Am I right?

How are you handling it at the store level as well as at the relationship level?

Thanks.

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.