Skip to content

Commit

Permalink
Cleanup
Browse files Browse the repository at this point in the history
  • Loading branch information
jamesdaniels committed Feb 9, 2019
1 parent 014fd8a commit 3211695
Show file tree
Hide file tree
Showing 10 changed files with 140 additions and 115 deletions.
18 changes: 3 additions & 15 deletions .github/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,29 +75,17 @@ If you'd like to contribute to EmberFire, run the following commands to get your
### Setup

* `git clone` this repository
* `npm install -g ember-cli bower gulp`
* `npm install`
* `bower install`
* `npm i`

### Running tests

* `ember test` OR
* `ember test --server`

##### Running tests against a specific version of ember-data

* `ember try:one <scenario>` where `<scenario>` is one of the scenarios in `config/ember-try.js`

Example:

```
ember try:one ember-data-canary
```

### Running the FireBlog demo app

* `ember server`
* Visit your app at [http://localhost:4200](http://localhost:4200).
* `npm run serve`
* Visit your app at [http://localhost:3000](http://localhost:3000).

### Using your local EmberFire workdir in another local project

Expand Down
24 changes: 16 additions & 8 deletions addon/adapters/firestore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { firestore } from 'firebase/app';

export type CollectionReferenceOrQuery = firestore.CollectionReference | firestore.Query;
export type QueryFn = (ref: CollectionReferenceOrQuery) => CollectionReferenceOrQuery;
export type QueryRecordFn = (ref: firestore.CollectionReference) => firestore.DocumentReference;

/**
* Persist your Ember Data models in Cloud Firestore
Expand Down Expand Up @@ -104,18 +105,19 @@ export default class FirestoreAdapter extends DS.Adapter.extend({
// @ts-ignore repeat here for the tyepdocs
firebaseApp: Ember.ComputedProperty<FirebaseAppService, FirebaseAppService>;

findRecord<K extends keyof ModelRegistry>(_store: DS.Store, type: ModelRegistry[K], id: string) {
return getDoc(this, type, id);
findRecord<K extends keyof ModelRegistry>(store: DS.Store, type: ModelRegistry[K], id: string) {
return this.queryRecord(store, type, ref => ref.doc(id));
}

findAll<K extends keyof ModelRegistry>(_store: DS.Store, type: ModelRegistry[K]) {
return rootCollection(this, type).then(queryDocs);
findAll<K extends keyof ModelRegistry>(store: DS.Store, type: ModelRegistry[K]) {
return this.query(store, type, ref => ref)
}

findHasMany<K extends keyof ModelRegistry>(store: DS.Store, snapshot: DS.Snapshot<K>, url: string, relationship: {[key:string]: any}) {
const adapter = store.adapterFor(relationship.type as never) as any; // TODO fix types
if (adapter !== this) {
return adapter.findHasMany(store, snapshot, url, relationship);
// TODO allow for different serializers, if not already working
return adapter.findHasMany(store, snapshot, url, relationship) as RSVP.Promise<any>;
} else if (relationship.options.subcollection) {
return docReference(this, relationship.parentModelName, snapshot.id).then(doc => queryDocs(doc.collection(collectionNameForType(relationship.type)), relationship.options.query));
} else {
Expand All @@ -126,30 +128,36 @@ export default class FirestoreAdapter extends DS.Adapter.extend({
findBelongsTo<K extends keyof ModelRegistry>(store: DS.Store, snapshot: DS.Snapshot<K>, url: string, relationship: {[key:string]: any}) {
const adapter = store.adapterFor(relationship.type as never) as any; // TODO fix types
if (adapter !== this) {
return adapter.findBelongsTo(store, snapshot, url, relationship);
// TODO allow for different serializers, if not already working
return adapter.findBelongsTo(store, snapshot, url, relationship) as RSVP.Promise<any>;
} else {
return getDoc(this, relationship.type, snapshot.id);
}
}

query<K extends keyof ModelRegistry>(_store: DS.Store, type: ModelRegistry[K], queryFn: QueryFn, _recordArray: DS.AdapterPopulatedRecordArray<any>) {
query<K extends keyof ModelRegistry>(_store: DS.Store, type: ModelRegistry[K], queryFn: QueryFn, _recordArray?: DS.AdapterPopulatedRecordArray<any>) {
return rootCollection(this, type).then(collection => queryDocs(collection, queryFn));
}

queryRecord<K extends keyof ModelRegistry>(_store: DS.Store, type: ModelRegistry[K], queryFn: QueryRecordFn) {
return rootCollection(this, type).then(queryFn).then(ref => ref.get());
}

shouldBackgroundReloadRecord() {
return false; // TODO can we make this dependent on a listener attached
}

updateRecord<K extends keyof ModelRegistry>(_store: DS.Store, type: ModelRegistry[K], snapshot: DS.Snapshot<K>) {
const id = snapshot.id;
const data = this.serialize(snapshot, { includeId: false });
// TODO is this correct? e.g, clear dirty state and trigger didChange; what about failure?
return docReference(this, type, id).then(doc => doc.update(data));
}

createRecord<K extends keyof ModelRegistry>(_store: DS.Store, type: ModelRegistry[K], snapshot: DS.Snapshot<K>) {
const id = snapshot.id;
const data = this.serialize(snapshot, { includeId: false });
// TODO remove the unnecessary get()
// TODO remove the unnecessary get(), handle in serializer if I can (noramlizeCreateResponse right?) also this fails if read permissions is denied
if (id) {
return docReference(this, type, id).then(doc => doc.set(data).then(() => doc.get()));
} else {
Expand Down
26 changes: 15 additions & 11 deletions addon/adapters/realtime-database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ import { get, set } from '@ember/object';
import { database } from 'firebase/app';

export type ReferenceOrQuery = database.Reference | database.Query;
export type QueryFn = (ref: ReferenceOrQuery) => ReferenceOrQuery;
export type ReferenceOrQueryFn = (ref: ReferenceOrQuery) => ReferenceOrQuery;
export type QueryFn = (ref: database.Reference) => ReferenceOrQuery;

/**
* Persist your Ember Data models in the Firebase Realtime Database
Expand Down Expand Up @@ -68,18 +69,19 @@ export default class RealtimeDatabaseAdapter extends DS.Adapter.extend({
// @ts-ignore repeat here for the tyepdocs
databaseURL?: string;

findRecord<K extends keyof ModelRegistry>(_store: DS.Store, type: ModelRegistry[K], id: string) {
return docReference(this, type, id).then(ref => ref.once('value'));
findRecord<K extends keyof ModelRegistry>(store: DS.Store, type: ModelRegistry[K], id: string) {
return this.queryRecord(store, type, ref => ref.child(id));
}

findAll<K extends keyof ModelRegistry>(_store: DS.Store, type: ModelRegistry[K]) {
return rootCollection(this, type).then(queryDocs);
findAll<K extends keyof ModelRegistry>(store: DS.Store, type: ModelRegistry[K]) {
return this.query(store, type, ref => ref)
}

findHasMany<K extends keyof ModelRegistry>(store: DS.Store, snapshot: DS.Snapshot<K>, url: string, relationship: {[key:string]: any}) {
const adapter = store.adapterFor(relationship.type as never) as any;
const adapter = store.adapterFor(relationship.type as never) as any; // TODO kill the any
if (adapter !== this) {
return adapter.findHasMany(store, snapshot, url, relationship);
// TODO allow for different serializers, if not already working
return adapter.findHasMany(store, snapshot, url, relationship) as RSVP.Promise<any>;
} else if (relationship.options.subcollection) {
throw `subcollections (${relationship.parentModelName}.${relationship.key}) are not supported by the Realtime Database, consider using embedded relationships or check out Firestore`;
} else {
Expand All @@ -91,9 +93,10 @@ export default class RealtimeDatabaseAdapter extends DS.Adapter.extend({
}

findBelongsTo<K extends keyof ModelRegistry>(store: DS.Store, snapshot: DS.Snapshot<K>, url: any, relationship: any) {
const adapter = store.adapterFor(relationship.type as never) as any;
const adapter = store.adapterFor(relationship.type as never) as any; // TODO kill the any
if (adapter !== this) {
return adapter.findBelongsTo(store, snapshot, url, relationship);
// TODO allow for different serializers, if not already working
return adapter.findBelongsTo(store, snapshot, url, relationship) as RSVP.Promise<any>;
} else {
return docReference(this, relationship.type, snapshot.id).then(ref => ref.once('value'));
}
Expand Down Expand Up @@ -123,13 +126,14 @@ export default class RealtimeDatabaseAdapter extends DS.Adapter.extend({
updateRecord<K extends keyof ModelRegistry>(_: DS.Store, type: ModelRegistry[K], snapshot: DS.Snapshot<K>) {
const id = snapshot.id;
const data = this.serialize(snapshot, { includeId: false });
// TODO is this correct? e.g, clear dirty state and trigger didChange; what about failure?
return docReference(this, type, id).then(ref => ref.set(data));
}

createRecord<K extends keyof ModelRegistry>(_: DS.Store, type: ModelRegistry[K], snapshot: DS.Snapshot<K>) {
const id = snapshot.id;
const data = this.serialize(snapshot, { includeId: false });
// TODO remove the unnecessary once('value')
// TODO remove the unnecessary once('value'), handle in serializer if I can (noramlizeCreateResponse right?) also this fails if read permissions is denied
if (id == null) {
return rootCollection(this, type).then(ref => ref.push(data).then(ref => ref.once('value')));
} else {
Expand All @@ -149,7 +153,7 @@ declare module 'ember-data' {
}
}

const queryDocs = (referenceOrQuery: ReferenceOrQuery, query?: QueryFn) => {
const queryDocs = (referenceOrQuery: ReferenceOrQuery, query?: ReferenceOrQueryFn) => {
const noop = (ref: database.Reference) => ref;
const queryFn = query || noop;
return getDocs(queryFn(referenceOrQuery));
Expand Down
14 changes: 14 additions & 0 deletions addon/mixins/realtime-route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import Mixin from '@ember/object/mixin';
import { subscribe, unsubscribe } from '../services/realtime-listener';
import DS from 'ember-data';

export default Mixin.create({
afterModel(model:DS.Model) {
subscribe(this, model);
return this._super(model);
},
deactivate() {
unsubscribe(this);
return this._super();
}
});
44 changes: 19 additions & 25 deletions addon/serializers/firestore.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import DS from 'ember-data';
import { firestore } from 'firebase';
import { get } from '@ember/object';

export type DocumentSnapshot = firestore.DocumentSnapshot | firestore.QueryDocumentSnapshot;
export type Snapshot = firestore.DocumentSnapshot | firestore.QuerySnapshot;
Expand All @@ -11,21 +10,16 @@ export default class FirestoreSerializer extends DS.JSONSerializer {
normalizeSingleResponse(store: DS.Store, primaryModelClass: DS.Model, payload: firestore.DocumentSnapshot, _id: string | number, _requestType: string) {
if (!payload.exists) { throw new DS.NotFoundError(); }
const { data, included } = normalize(store, primaryModelClass, payload) as any;
const meta = { ...extractMeta(payload), ref: payload.ref};
const meta = extractMeta(payload);
return { data, included, meta };
}

normalizeArrayResponse(store: DS.Store, primaryModelClass: DS.Model, payload: firestore.QuerySnapshot, _id: string | number, _requestType: string) {
const noramlizedRecords: any[] = []
const included: any[] = [];
const query = payload.query;
payload.forEach(snapshot => {
const { data, included: includes } = normalize(store, primaryModelClass, snapshot);
noramlizedRecords.push(data);
includes.forEach(record => included.push(record));
});
const meta = { ...extractMeta(payload), query };
return { data: noramlizedRecords, included, meta };
const normalizedPayload = payload.docs.map(snapshot => normalize(store, primaryModelClass, snapshot));
const included = new Array().concat(...normalizedPayload.map(({included}) => included));
const meta = extractMeta(payload)
const data = normalizedPayload.map(({data}) => data);
return { data, included, meta };
}

}
Expand All @@ -45,19 +39,19 @@ export const normalize = (store: DS.Store, modelClass: DS.Model, snapshot: Docum
return { data, included };
}

// TODO clean up
const extractMeta = (snapshot: any) => {
const meta: any = Object.assign({}, snapshot.metadata);
const keyPath = get(snapshot, '_key.path');
meta.database = get(snapshot, '_firestore._databaseId');
meta.canonicalId = keyPath ? `${keyPath}|f:|ob:__name__asc,` : get(snapshot, '_originalQuery.memoizedCanonicalId');
meta.firestore = get(snapshot, '_firestore');
meta.client = get(snapshot, '_firestore._firestoreClient');
const queryDataByTarget = get(snapshot, '_firestore._firestoreClient.localStore.queryDataByTarget') || {};
const queryData = meta.canonicalId && Object.keys(queryDataByTarget).map(tid => queryDataByTarget[tid]).find((target:any) => target.query.memoizedCanonicalId === meta.canonicalId);
meta.queryData = queryData || {};
meta.version = get(snapshot, '_document.version') || queryData && queryData.snapshotVersion;
return meta;
function isQuerySnapshot(arg: any): arg is firestore.QuerySnapshot {
return arg.query !== undefined;
}

const extractMeta = (snapshot: firestore.DocumentSnapshot|firestore.QuerySnapshot) => {
const fromCache = snapshot.metadata.fromCache;
const hasPendingWrites = snapshot.metadata.hasPendingWrites;
if (isQuerySnapshot(snapshot)) {
const query = snapshot.query;
return { fromCache, hasPendingWrites, query };
}
const ref = snapshot.ref;
return { fromCache, hasPendingWrites, ref };
}

const normalizeRelationships = (store: DS.Store, modelClass: DS.Model, attributes: any) => {
Expand Down
Loading

0 comments on commit 3211695

Please sign in to comment.