-
Notifications
You must be signed in to change notification settings - Fork 1
/
hasone.js
188 lines (171 loc) · 5.53 KB
/
hasone.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
/*jshint laxcomma: true, smarttabs: true, node: true, esnext: true, unused: true*/
'use strict';
/**
* Field type to handle hasone / belongsto fields for thinky models
* @module tastypie-rethink/lib/fields/hasone
* @author Eric Satterwhite
* @since 2.1.0
* @requires path
* @requires tastypie
* @requires tastypie/lib/fields/api
* @requires mout/string/typecast
* @requires tastypie/lib/utility
*/
require('../addrelation'); // patch thinky document#addRelation
var tastypie = require( 'tastypie' )
, ApiField = require( 'tastypie/lib/fields/api' )
, typecast = require( 'mout/string/typecast' )
, kindOf = require('mout/lang/kindOf')
, toModule = require( 'tastypie/lib/utility').toModule
, Class = tastypie.Class
, HasOne
;
/**
* @constructor
* @alias module:tastypie-rethink/lib/fields/hasone
* @param {Object} options
* @param {?module:tastypie/lib/resource} to A resource class to represent a related object
* @param {Boolean} [full=false] If set to tree the entire value of the related resoures will be returned
* @param {Boolean} [minimal=false] If set to true and `full` is set to false, a minimalistic representation of the related resource will be returned - including the id field, uri field, and a single display value
* @example var x = new hasone.js.THING();
*/
HasOne = new Class({
inherits: ApiField
,options:{
to:null
,minimal: false
,full: false
}
,is_related: true
,constructor: function(options){
this.parent('constructor',options);
this.instance = new this.cls();
}
/**
* Converts a serialized value into a full resource instance value
* @method module:tastypie-rethink/lib/fields/hasone#hydrate
* @param {module:tastypie/lib/resource~Bundle} bundle
**/
,hydrate: function( bundle, cb ){
this.parent('hydrate', bundle,( err, value )=>{
if( !value ){
return cb(err, value);
}
return this.toInstance( value,(err, instance)=>{
this.instance.full_hydrate(this.instance.bundle(bundle.req, bundle.res, instance, instance) ,( err, result, saved )=>{
cb( err, result.object );
});
});
});
}
,toValue: function( bundle, cb ){
this.parent('hydrate', bundle, cb );
}
,toInstance: function( value, cb ){
let saved = false;
value = typecast( value );
if( !value ){
return cb(null, value);
}
switch(kindOf(value)){
case 'String':
let bits, data;
// might be a URI
bits = value.split('/');
data = {};
data[this.instance.options.objectTpl._pk] = bits[bits.length-1];
value = new this.instance.options.objectTpl( data );
value.link = true
saved = true;
break;
case 'Object':
saved = !!value[ this.instance.options.objectTpl._pk ];
value = new this.instance.options.objectTpl( value );
value.modified = true;
break;
}
if( saved ){
value.setSaved()
cb( null, value)
} else {
value.save( cb );
}
}
/**
* Used to distil an object in to something suitable for serialization
* @method module:tastypie-rethink/lib/fields/hasone#dehydrate
* @param {Mixed} obj A value to dehydrate
* @param {Function} callback A node style callback to be called when execution is complete
**/
,dehydrate: function( obj, cb ){
if( !this.instance.options.apiname ){
this.instance.setOptions({
apiname: this.resource.options.apiname
});
}
obj = obj.toJSON ? obj.toJSON() : obj;
var attribute = this.options.name;
this.parent('dehydrate',obj, ( err, value ) => {
if( err || !value ){
return cb && cb( err, null );
}
if( this.options.full ){
this.instance.full_dehydrate( obj[attribute] ? obj[attribute] : value , null, cb );
} else if( this.options.minimal ){
this.to_minimal( obj[attribute] ? obj[attribute] : value , cb );
} else {
cb( err, value && this.instance.to_uri( value ) );
}
});
}
/**
* converts a full object in to a minimal representation
* @method module:tastypie-rethink/lib/field/hasone#to_minimal
* @param {Object} obj A template object instance to introspect
* @param {Function} callback A node style call back to be called when execution is complete
**/
,to_minimal: function( obj, cb ){
var label = this.instance.options.labelField || 'display'
, related_field
;
related_field = this.instance.fields[label];
related_field.dehydrate( obj, this._to_minimal.bind(this, obj, related_field, label, cb ));
}
/**
* Used to construct a minimal representation of an object
* @private
* @method module:tastypie/rethink/lib/fields/hasone#_to_minimal
* @param {Object} obj
* @param {module:tastypie/lib/field/related} field The field instance from the related resource being dehydrated
* @param {String} label The name of the field being dehydrated
* @param {Function} callback The callback function to be executed when execution is complete
* @param {Error} [?err] An error object from previous field dehydration
* @param {Object} value The value of the field in question
**/
,_to_minimal: function( obj, related_field, label, cb, err, value ){
var out = {};
out.uri = this.instance.to_uri( obj );
out.id = this.instance.pk( obj );
out[ label ] = value;
label = related_field = null;
cb( null, out );
}
});
Object.defineProperties(HasOne.prototype,{
cls: {
enumerable: false
,writeable: false
,get: function(){
if( typeof this.options.to === 'string'){
return toModule( this.options.to );
}
return this.options.to;
}
}
});
Object.defineProperty(tastypie.fields,'hasone',{
get:function(){
return HasOne;
}
});
module.exports = HasOne;