Rails like models using ES6.
MIT License
Deprication Warning: This package will be discontinued and replaced by next-model npm package with Meteor connector
Rails like models using ES6.
Smart Record gives you the ability to:
Fix typos
Add more examples
There are already many tests, but not every test case is covered.
where
/all
calls should be cached and refreshed by reload()
includes
prefetches relations with two db queries (fetch records => pluck ids => fetch related records by ids) instead of one query per related model.
User.includes({address: {}})
, Profile.includes({user: {address: {}}})
Add sorting to scopes
Add order functions Profile.males.order({name: 1})
Migrations
Record versioning
Support for Apollo
To keep the configuration as short as possible, its recommended to use the following conventions:
createdAt
someOtherValue
Address
Chat
ChatMessage
User
- userId
Chat
- chatId
ChatMessage
- chatMessageId
There is currently no dedicated example, but take a look at tests/models.js
for some basics.
Initializes new record from relation while maintaining the current scope.
address = Address.build({street: '1st street'});
address.isNew === true;
address = user.addresses.build();
address.userId === user.id;
Tries to create a new record with the same scoped attributes defined in the relation. Throws an error if callbacks abort creation.
Server: Returns the initialized record if the validation or insert fails.
address = Address.create({street: '1st street'});
address.isValid !== address.isNew;
Client:
Returns a Promise
which returns the created record on success or the initialized if sth. goes wrong.
Address.create({
street: '1st street'
}).then(function(address){
address.isNew === false;
}).catch(function(address){
address.isNew === true;
});
Address = class Address extends SmartModel {
static get belongsTo() {
return {
user: {}
}
}
};
address = Address.create({userId: id});
user = address.user;
address = Address.build();
address.user = user;
address.userId === user.id;
User = class User extends SmartModel {
static get hasMany() {
return {
addresses: {}
}
}
};
user.addresses.all();
address = user.addresses.create();
User = class User extends SmartModel {
static get hasMany() {
return {
addresses: {dependent: 'destroy'}
}
}
};
User = class User extends SmartModel {
static get hasOne() {
return {
profile: {}
}
}
};
user.profile;
User = class User extends SmartModel {
static get hasOne() {
return {
profile: {dependent: 'destroy'}
}
}
};
Profile = class Profile extends SmartModel {
static get males() {
return this.scope({selector: {gender: 'male'}});
}
static get females() {
return this.scope({selector: {gender: 'female'}});
}
static get young() {
return this.scope({selector: {age: {$lte: 18}}});
}
static get old() {
return this.scope({selector: {age: {$gt: 18}}});
}
static withName(name) {
return this.scope({selector: {name: name}});
}
}
Profile.males.all();
Profile.males.where({age: 21});
profile = Profile.males.build();
profile.gender === 'male';
Profile.males.young;
Profile.males.young.where({});
Queries are allways relative to the parent scopes and the own default scope.
Retruns one matching record (equivalent to findOne from Meteor.Collection).
Address.find(selector, options);
Profile.males.young.find(selector, options);
Retruns an cursor (equivalent to find from Meteor.Collection).
Address.cursor(selector, options);
Profile.males.young.cursor(selector, options);
Retruns an array of records (equivalent to find.fetch from Meteor.Collection).
Address.where(selector, options);
Profile.males.young.where(selector, options);
Retruns the count of the matching records (equivalent to find.count from Meteor.Collection).
Address.count(selector, options);
Profile.males.young.count(selector, options);
Checks if there is any matching item with selector.
Address.hasAny(selector);
Profile.males.young.hasAny(selector);
Checks if there is no matching item with selector.
Address.isEmpty(selector);
Profile.males.young.isEmpty(selector);
Retruns the first matching record regarding the creation order.
Address.first(selector, options);
Profile.males.young.first(selector, options);
Retruns the last matching record regarding the creation order.
Address.last(selector, options);
Profile.males.young.last(selector, options);
Define the writeable columns and types. The schema uses the syntax of aldeed:simple-schema.
Possible options: type
, defaultValue
, autoValue
, optional
, min
, max
Address = class Address extends SmartModel {
static get schema() {
return {
street: {type: String, defaultValue: ''},
postalCode: {type: String, defaultValue: ''},
city: {type: String, defaultValue: '', min: 1},
country: {type: String, defaultValue: 'Germany'},
note: {type: String, defaultValue: '', optional: true}
}
}
}
The class name (model.name) is set automatically while defining a Class. Scopes return a anonymous Class. Please get the model/Class name with this function.
Profile.modelName === Profile.males.modelName; // = Profile
Profile.name !== Profile.males.name; // = _class
address = Address.build();
address.isNew === true;
address.save();
address.isNew === false;
address = Address.build();
address.isPersistent === false;
address.save();
address.isPersistent === true;
Even when MongoDB is using _id as property saving the identifier. With id
you can allways access _id
.
address.id === address._id;
There is also an getter for the id which is named like the model name. This is really helpful for nested urls.
route = '/list/:listId/item/:itemId';
list = List.find({listId});
item = Item.find({itemId});
The itemType is the same as modelName - just on an instance level.
Profile.first().itemType === Profile.males.first().itemType;
Returns true if record is valid.
Please note: isValid is only set after calling save
, update
, validate
or create
.
address.isValid === true;
address.save();
address.isValid === true;
delete address.requiredField;
address.isValid === true;
address.validate();
address.isValid === false;
Errors are set by save
and valudate
. After you called one of this functions you can read the validation errors.
profile = Profile.save();
profile.errors === [{
column: 'lastname', error: 'required'
}];
Returns an object which contains all properties. You can pick several columns by passing the pick
options.
address.attributes() === {
street: "1st street",
city: "New York",
country: "USA"
};
address.attributes({pick: ['street']}) === {
street: "1st street"
};
Sets the createdAt field to the current time if not yet set and allways sets the updatedAt propierty to now.
address = Address.build();
address.createdAt === address.updatedAt === undefined;
address.touch();
address.createdAt === address.updatedAt !== undefined;
address.touch();
address.createdAt !== address.updatedAt;
Saves the record unless validation failes or an before callback breaks the call.
options:
skipValidation
skips validation if presentskipCallbacks
skip all callbacks or just specific onesskipTouch
do not update timestampsskipApplyDefaults
do not apply the default valuesServer: Returns the initialized record if the validation or insert fails.
address = Address.build({street: '1st street'});
address.save();
address.save({skipValidation: true});
Client:
Returns a Promise
which returns the created record on success or the initialized if sth. goes wrong.
address = Address.build({street: '1st street'});
address.save().then(function(address){
address.isNew === false;
}).catch(function(address){
address.isNew === true;
});
Destroys the current model instance.
Address.count({_id: address.id}) === 1;
address.destroy();
Address.count({_id: address.id}) === 0;
Sets multiple attributes by passing an object.
address.extend({
street: "1st street",
city: "New York",
country: "USA"
});
Sets multiple attributes by passing an object, and saves the record afterwards.
address.update({
street: "1st street",
city: "New York",
country: "USA"
});
Profile = class Profile extends SmartModel {
static get schema() {
return {
firstname: {type: String},
lastname: {type: String}
}
}
get name() {
return `${this.firstName} ${this.lastName}`;
}
}
profile = Profile.build({
firstname: 'Foo',
lastname: 'Bar'
});
profile.name === 'Foo Bar';
0.2.0
2016-03-28
Required now Meteor 1.3
Breaking Changes
defaultScope
, schema
, modelName
, collection
, collectionName
, belongsTo
, hasOne
, hasMany
are now static getters. Don't call them as function anymore.
localCollection
class property to create models for client or server only models.attrAccessors
to define properties which can passed to the model which are not passed to the database.0.1.0
2016-02-21
Used the aldeed:simple-schema package to define schema and validation.
Added sample how to use this package with zaku:smart-form.
0.0.4
2016-02-14
Added id alias (eg. List.listId
) to instance and selector List.find({listId})
0.0.3
2016-02-10
dependent: 'destroy'
for hasOne
and hasMany
relations.save
, update
, create
, destroy
:
beforeCommit
afterCommit
.hasAny(selector)
.isEmpty(selector)
0.0.2
2016-02-09
Released package on atmosphere
0.0.1
2016-02-09
Initial commit with the following functions:
.modelName()
.scope(options)
.defaultScope()
.build(attrs)
.create(attrs)
.find(selector, options)
.cursor(selector, options)
.where(selector, options)
.count(selector, options)
.first(selector, options)
.last(selector, options)
.all(options)
.destroyAll(selector, options)
.getSchema()
.collection()
.allow()
.deny()
.collectionName()
overrideable
.schema()
overrideable
.belongsTo()
overrideable
.hasMany()
overrideable
.hasOne()
overrideable
#isValid
#isNew
#isPersistent
#id
#itemType
#errors
#attributes(options)
#validate(skipCallbacks)
#touch()
#save(options)
#destroy(skipCallbacks)
#extend(attrs)
#update(attrs, options)