In Meteor, where do I model my business rules? - model-view-controller

Beginner question : I've worked through the Try Meteor tutorial. I've got fields in my HTML doc, backed by helper functions that reference collections, and BOOM --> the fields are updated when the data changes in the DB.
With the "Hide completed" checkbox, I've also seen data-binding to a session variable. The state of the checkbox is stored in the Session object by an event handler and BOOM --> the list view is updated "automatically" by its helper when this value changes. It seems a little odd to be assigning to a session object in a single page application.
Through all this, my js assigns nothing in global scope, I've created no objects, and I've mostly seen just pipeline code, getting values from one spot to another. The little conditional logic is sprayed about wherever it is needed.
THE QUESTION... Now I want to construct a model of my business data in javascript, modelling my business rules, and then bind html fields to this model. For example, I want to model a user, giving it an isVeryBusy property, and a rule that sets isVeryBusy=true if noTasks > 5. I want the property and the rule to be isolated in a "pure" business object, away from helpers, events, and the meteor user object. I want these business objects available everywhere, so I could make a restriction, say, to not assign tasks to users who are very busy, enforced on the server. I might also want a display rule to only display the first 100 chars of other peoples tasks if a user isVeryBusy. Where is the right place to create this user object, and how do I bind to it from my HTML?

You can (and probably should) use any package which allows you to attach a Schema to your models.
Have a look at:
https://github.com/aldeed/meteor-collection2
https://github.com/aldeed/meteor-simple-schema
By using a schema you can define fields, which are calculated based on other fields, see the autoValue property: https://github.com/aldeed/meteor-collection2#autovalue
Then you can do something like this:
// Schema definition of User
{
...,
isVeryBusy: {
type: Boolean,
autoValue: function() {
return this.tasks.length > 5;
}
},
...
}
For all your basic questions, I can strongly recommend to read the DiscoverMeteor Book (https://www.discovermeteor.com/). You can read it in like 1-2 days and it will explain all those basic questions in a really comprehensible way.
Best Regards,

There is a very good package to implement the solution you are looking for. It is created by David Burles and it's called "meteor-collection-helper". Here it the atmosphere link:
You should check the link to see the examples presented there but according to the description you could implement some of the functionality you mentioned like this:
// Define the collections
Clients = new Mongo.Collection('clients');
Tasks = new Mongo.Collection('tasks');
// Define the Clients collection helpers
Clients.helpers({
isVeryBusy: function(){
return this.tasks.length > 5;
}
});
// Now we can call it either on the client or on the server
if (Meteor.isClient){
var client = Clients.findOne({_id: 123});
if ( client.isVeryBusy() ) runSomeCode();
}
// Of course you can use them inside a Meteor Method.
Meteor.methods({
addTaskToClient: function(id, task){
var client = Clients.findOne({_id: id});
if (!client.isVeryBusy()){
task._client = id;
Tasks.insert(task, function(err, _id){
Clients.update({_id: client._id}, { $addToSet: { tasks: _id } });
});
}
}
});
// You can also refer to other collections inside the helpers
Tasks.helpers({
client: function(){
return Clients.findOne({_id: this._client});
}
});
You can see that inside the helper the context is the document transformed with all the methods you provided. Since Collections are ussually available to both the client and the server, you can access this functionality everywhere.
I hope this helps.

Related

Parse-Server prevent fields from being added automatically

Right now, if I add a field to a Parse object and then save it, the new column shows up in the Parse dashboard.
For example, after running:
let media = new Parse.Object("Media");
media.set("foo", "bar");
await media.save();
I will have a new column called foo.
Is it possible to prevent this from happening?
Yes. This can be done using class-level permissions, which allow you to prevent fields being added to classes.
Parse lets you specify what operations are allowed per class. This lets you restrict the ways in which clients can access or modify your classes.
...
Add fields: Parse classes have schemas that are inferred when objects are created. While you’re developing your app, this is great, because you can add a new field to your object without having to make any changes on the backend. But once you ship your app, it’s very rare to need to add new fields to your classes automatically. You should pretty much always turn off this permission for all of your classes when you submit your app to the public.
You would have to add a beforeSave trigger for every one of your classes, keep a schema of all your keys, iterate over the request.object's keys, and see if there are any that do not belong in your schema. You can then either un-set them and call response.success(), or you can call response.error() to block the save entirely, preferably with a message indicating the offending field(s).
const approvedFields = ["field1", "field2", "field3"];
Parse.Cloud.beforeSave("MyClass", function(request, response) {
let object = request.object;
for( var key in object.dirtyKeys() ) {
if( approviedFields.indexOf(key) == -1 ) return response.error(`Error: Attempt to save invalid field: ${key});
}
response.success();
});
Edit:
Since this got a little attention, I thought I'd add that you can get the current schema of your class. From the docs: https://docs.parseplatform.org/js/guide/#schema
// create an instance to manage your class
const mySchema = new Parse.Schema('MyClass');
// gets the current schema data
mySchema.get();
It's not clear if that's async or not (you'll have to test yourself, feel free to comment update the answer once you know!)
However, once you have the schema, it has a fields property, which is an object. Check the link for what those look like.
You could validate an object by iterating over it's keys, and seeing if the schema.fields has that property:
Parse.Cloud.beforeSave('MyClass', (request, response) => {
let object = request.object;
for( var key in object.dirtyKeys() ) {
if( !schema.fields.hasOwnProperty(key) ) < Unset or return error >
}
response.success();
}
And an obligatory note for anyone just starting with Parse-Server on the latest version ,the request scheme has changed to no longer use a response object. You just return the result. So, keep that in mind.

Validate breeze save bundle before going to the breeze controller

Everywhere I look about validating the response before saving to the DB, in breeze, they are overriding the BeforeSaveEntity or BeforeSaveValidation.
e.g. breezejs issues with the save bundle. Is there anyway we can validate the savebundle before calling the saveChanges(), like in the repository level?
I want to pass the JObject savebundle from the controller to the relevant repository and do a few things there:
1) Check if the user has permission to save this entity
2) Do business-logic level validation
3) Do entity level operations such as updating the changedDate and changedUser, add default values to some other entity etc...
These are more like business-logic level operations and in our application we have like 20+ such entities that get saved from different parts of the application. If we override BeforeSaveEntity() we are doing all such business logic level validations for all entities inside the DataContext. Like
`if (entityInfo.Entity.GetType() == typeof(MyEntityTypeModel)) {
}`
I don't think if-else or case condition for 20+ entities is a good design. Besides, we have a clear separation of concerns through the use of repositories, so I think that's where this should be done.
How do we manipulate/validate the savebundle in such case?
Use the BeforeSaveEntities method ( documented here: http://www.getbreezenow.com/breeze-sharp-documentation/contextprovider). With this method you can work with ALL of the entities of a specified type without having to perform an 'if' test on each one.
Code might look something like this:
ContextProvider.BeforeSaveEntitiesDelegate = CheckFreightOnOrders;
return ContextProvider.SaveChanges(saveBundle);
private Dictionary<Type, List<EntityInfo>> CheckFreightOnOrders(Dictionary<Type, List<EntityInfo>> saveMap) {
List<EntityInfo> entityInfos;
// extract just those entities of type 'Order'
if (saveMap.TryGetValue(typeof(Order), out orderEntityInfos)) {
// then iterate over them.
foreach (var entityInfo in orderEntityInfos) {
CheckFreight(entityInfo);
}
}
return saveMap;
}

Knockout Mapping - Fill Observable Arrays keeping Items' methods

I've been facing a problem that is basically the following:
I have a knockout ViewModel which contains observable arrays of items with observable properties and methods.
I need to pull data from the server. The methods need to exist after data is taken from server. So I create a new ViewModel and then update its value from what comes from server. (THIS DOES NOT WORK, THE RESULTING ARRAY HAS NO ITEMS)
If I create, with mapping, a new object using var newObj = ko.mapping.fromJS(data) the resulting Array has items, but its items have no methods. It spoils my Bindings.
The fiddle of my problem: http://jsfiddle.net/claykaboom/R823a/3/ ( It works util you click in "Load Data From The Server" )
The final question is: What is the best way to have items on the final array without making the loading process too cumbersome, such as iterating through every item and filling item's properties in order to keep the previously declared methods?
Thanks,
I changed your code little bit. Check this version of JSFiddle.
var jsonFromServer = '{"ModuleId":1,"Metadatas":[{"Id":1,"MinValue":null,"MaxValue":null,"FieldName":"Teste","SelectedType":"String","SelectedOptionType":null,"IsRequired":true,"Options":[]}]}';
Your code doesnt work because your jsonFromServer variable does not contain methods we need at binding like you described in your question. ( -- > Metadatas )
So we need to define a custom create function for Metadata objects at the mapping process like this :
var mapping = {
'Metadatas': {
create: function(options) {
var newMetaData = new MetadataViewModel(options.parent);
newMetaData.Id(options.data.id);
newMetaData.FieldName(options.data.FieldName);
newMetaData.SelectedType(options.data.SelectedType);
newMetaData.SelectedOptionType(options.data.SelectedOptionType);
newMetaData.IsRequired(options.data.IsRequired);
newMetaData.Options(options.data.Options);
// You can get current viewModel instance via options.parent
// console.log(options.parent);
return newMetaData;
}
}
}
Then i changed your load function to this :
self.LoadDataFromServer = function() {
var jsonFromServer = '{"ModuleId":1,"Metadatas":[{"Id":1,"MinValue":null,"MaxValue":null,"FieldName":"Teste","SelectedType":"String","SelectedOptionType":null,"IsRequired":true,"Options":[]}]}';
ko.mapping.fromJSON(jsonFromServer, mapping, self);
}
You dont have to declare a new viewModel and call ko.applyBindings again. Assigning the updated mapping to current viewModel is enough. For more information check this link. Look out for customizing object construction part.
The final question is: What is the best way to have items on the final
array without making the loading process too cumbersome, such as
iterating through every item and filling item's properties in order to
keep the previously declared methods?
As far as i know there is no easy way to do this with your object implemantation. Your objects are not simple. They contains both data and functions together. So you need to define custom create function for them. But if you can able to separate this like below then you dont have to customize object construction.
For example seperate the MetadataViewModel to two different object :
--> Metadata : which contains only simple data
--> MetadataViewModel : which contains Metadata observableArray and its Metadata manipulator functions
With this structure you can call ko.mapping.fromJSON(newMetaDataArray , {} , MetadataViewModelInstance.MetadataArray) without defining a custom create function at the mapping process.

Can I include a convenience query in a Doctrine 2 Entity Method?

I'd like to include some additional functions in my Doctrine 2 entities to contain code that I'm going to have to run quite frequently. For example:
User - has many Posts
Post - has a single user
I already have a function $user->getPosts(), but this returns all of my posts. I'm looking to write a $user->getActivePosts(), which would be like:
$user->getPosts()->where('active = true') //if this were possible
or:
$em->getRepository('Posts')->findBy(array('user'=>$user,'active'=>true)) //if this were more convenient
As far as I can tell, there's no way to get back to the entity manager though the Entity itself, so my only option would be
class User {
function getActivePosts() {
$all_posts = $this->getPosts();
$active_posts = new ArrayCollection();
foreach ($all_posts as $post) {
if ($post->getActive()) {
$active_posts->add($post);
}
}
return $active_posts;
}
However, this requires me to load ALL posts into my entity manager, when I really only want a small subset of them, and it requires me to do filtering in PHP, when it would be much more appropriate to do so in the SQL layer. Is there any way to accomplish what I'm looking to do inside the Entity, or do I have to create code outside of it?
I think you should implement the method on the PostRepository rather than on the entity model.
I try to keep all model related logic in the repositories behind "domain specific" methods. That way if you change the way you represent whether a post is active or not, you only have to change the implementation of a single method instead of having to find all the active = true statements scattered around in your application or making changes in an "unrelated" entity model.
Something like this
PostRepository extends EntityRepository {
public function findActiveByUser($user){
// whatever it takes to get the active posts
}
}

How can I "defang" a Doctrine2 Entity?

I'd like to create an "API-like" layer in my code that effectively cordons-off database access to higher level code. For example, I might have the following function:
class MyApi {
private $my_user_id;
function getContacts() {
$contacts = $em->getRepository('Contacts')->findByOwner($this->my_user_id);
$em->clear();
return $contacts;
}
function getGroups() {
$groups = $em->getRepository('Groups')->findByOwner($this->my_user_id);
//hydrate each group's contacts list
foreach ($groups as $group) {
$group->getContacts();
}
$em->clear();
return $groups;
}
}
I'm using $em->clear() to detach the Entities from the EntityManger before returning them, so my Views can't accidentally modify managed entities. However, I run into problems when I want to compare entities returned by two sequential API functions. Ideally, I'd like a view/controller to contain:
$my_contacts = $myapi->getContacts();
$my_groups = $myapi->getGroups();
foreach($my_groups as $group) {
foreach ($my_contacts as $contact) {
if ($group->getContacts()->contains($contact)) {
echo "{$group->getName()} contains {$contact->getName()}!<br/>";
} else {
echo "{$group->getName()} does not contain {$contact->getName()}!<br/>";
}
}
}
However, since I detached all of the Contacts from the EntityManager at the end of the getContacts API call, the objects returned by $group->getContacts() are different PHP objects than those returned by $api->getContacts(), so the contains() function doesn't work properly.
Do I have any options for "defanging" my entities, making them effectively read-only, without giving up the benefits that the EntityManager provides? (Such as all managed entities representing the same database entry being the same object, being able to further hydrate associated objects after they've been passed back from the API, etc.)
Why would you worry that your views are going to make changes that will be committed back to the database? If your views don't know about the EM (and they shouldn't), any changes they make to the entities will disappear at the end of the request.
The only other option I can think of is to hydrate your results as arrays when they're destined to be fed to the view script. But that gives up a lot of handy functionality.
Maybe this is a little late, but it can be useful for anyone who still needs answer on this issue...
I think there is missing a Domain Driven Design principle here: Command Query Separation.
On every object you can only have two kind of methods:
doSomething() {
//this kind of method can change the internal state
}
getSomething() {
//this kind of method NEVER changes internal state
}
Keeping proper MVC in mind, views should only need get-methods and they can never change a thing.
Detaching you entities is not necessary and no worries are needed, if keeping just CQS and MVC in mind.

Resources