How do I run a callback function after a SailsJS REST Blueprint? - javascript

Is there a best practice for a callback after a blueprint route in SailsJS 11.x?
I have an API where Users can submit Ideas. After the Idea is created, if it's the User's first Idea they should get a Badge. As far as I can tell there are two places to put this functionality.
Copy/Override the Sails rest blueprint, add the functionality to the Idea.create() callback function.
Add a lifecycle callback for the afterCreate action on the Idea model.
I'm assuming any functionality I write in terms of granting badges should be put in a Service so it can be shared among the different models. It seems like option 1 wouldn't be as tightly coupled, but would lead to fatter controllers.

Overriding the "create" blueprint will mean that every model will use that code when handling a request to their POST /<modelName> route, which is probably not what you want. The lifecycle callback will work fine for granting the badge, but if you're looking to modify the response based on whether or not a badge was granted, this approach won't work--but you could easily send a socket notification from the lifecycle callback, using User.message().
The third option is just to override the create action in IdeaController.js. There's not all that much to it, especially if you let Waterline handle most of the field validation for you. In essence, it's just:
create: function(req, res) {
Idea.create(req.params.all()).exec(function(err, idea) {
if (err) {return res.negotiate(err);}
// ... do some stuff with the idea and send a response ...
});
}

I would go for the second option, however you should note that afterCreate gets triggered when testing via barrels and adding items, which might be an issue for the logic in afterCreate. (Ex: A user has 3 ideas, 1 badge from the first idea, you add them to barrels fixtures. When barrels is adding the first idea it will trigger the afterCreate then it will add the badge from the fixture resulting in the user having 2 duplicate badges.)

Related

Detecting changes on database table column status

I am having a project in Laravel. In database I have a status column, which shows if exam is started or not. I had an idea in the waiting room checking every single second if the status was changed or not, if changed to 1, when the exam starts, but I am so new to Laravel and everything else, that I even don't get the main idea how I could do this, I don't ask for any code, just for the lead, to move on. yeah, hope someones gets me. Thanks if someone answers me.
Check about laravel cron jobs. You will need a class implementing ShouldQueue interface and using Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
With regards to the storage of the jobs i do recommend Redis or SQS.
In order to keep monitoring the queue in production think about installing supervisor.
Further information here: Queues
Your plan can work, it is called polling.
Basically, you will want to call
setInterval(function() {
//your code here
}, 1000);
setInterval is a function that receives two parameter. The first is a callback function, that will periodically be executed and the second is the length of the period in milliseconds (1000 milliseconds is a second).
Now, you will need to implement your callback function (Javascript, of course) to send an AJAX request to a Laravel action. You will need to look into XMLHttpRequest and its usages, or you can use some libraries to simplify your task, like jQuery or Axios.
On Laravel's side you will need to implement an action and a Route for it. (read this: https://appdividend.com/2022/01/22/laravel-ajax/)
Your Laravel will need to load data from your database, you can use Eloquent for this purpose or raw queries and then respond the POST request with the result.
Now, in your Javascript at the AJAX request's code you will need to have a callback function (yes, a callback inside a callback) which will handle the response and the the changes.
What about leveraging Observers? Also instead of having a status boolean, you could take a similar approach that Laravel has done for soft deletes and set exam_started_at. This way you can also keep track of time stamp and state all in one column. Also, observers are immediate rather than pushing them into a queue. Then generate a websocket event that can report back to your front end, if needed.
check out Laravel observer and soft delete documentation.
I know you specified "when the column on db changes..." but if it's not a strict-requirement you might want to consider implementing event-based architecture. Laravel has support for model events, which essentially allows you to run certain assertions and controls when a model created, updated, deleted etc.
class Exam extends Model
protected static function booted()
{
static::updated(function ($exam) {
if($exam->status=='your-desired-status'){
//your actions
}
//you can even in cooperate change controls
if ($exam->isDirty('status')){
//means status column changed
});
}
}
Of course this solution applies only if Database in question is in Laravel's reach. If database data changes outside the Laravel application these event listeners won't help at all.

Transfer alerts or notifications to next route with Angular 5

I am building a CRUD (Create, Read, Update, Delete) form with Angular 5 and want to navigate to the route showing an overview of all already added elements including the new one.
this.myElement.create({
//... Do some API magic
}).subscribe(() =>
this.router.navigate(['/overview']);
);
After updating the database the user gets router.navigate'd to the overview. There should be displayed a message (alert, notification, toast or what ever) whether the change was successfull or not.
Possible aproaches:
Do I have to implement an own service to transfer informations like this one?
Is there a built-in solution in Angular?
Should I use something like an event handler or oberserver watching for any CRUD methods?
You need to be careful where you implement this alert. If it's a service, you might do
.subscribe(() =>
this.toast.show('Task is finished');
this.router.navigate(['/overview']);
);
Since you are switching routes, the toast component has to be in the parent templates of <router> or up.
If the service is designed targeting the body tag, ex. using position: fixed. Then you are safe no matter what.
I don't think the entire page will get refreshed if that's your concern.
Just to answer your question
Do I have to implement an own service to transfer informations like this one?
YES, not necessarily you, but someone.
Is there a built-in solution in Angular? Maybe, because you can just put an element that positioned to the screen as a simple alert. Angular doesn't do anything specific for this task.
Should I use something like an event handler or oberserver watching for any CRUD methods? No need, you want to finish this alert before moving to others. However if you want others to know an alert has been fired, then YES, you want that service to publish an observerable.

Best practice to change default blueprint actions in sails.js

I'm looking for an answer to what is the best practice to customize default sails.js CRUD blueprints. The simple example of what I mean is let's say I need to create SomeModel, using standard blueprint POST \someModel action, and also I want to get information about authenticated user from req object and set this property to req.body to use values.user.id in beforeCreate function:
module.exports = {
attributes: {
// some attributes
},
beforeCreate: function (values, cb) {
// do something with values.user.id
}
};
I'm very new to sails.js and don't want to use anti-patterns, so I'm asking for inputs on what is the right way to handle such cases. Also it would be great to have some good resources on that topic.
There are a few options open to you which you've identified, and as usual, it depends on your use case. Here are some thoughts on the three options you listed in your comment:
Override controller/create - Of the three, this is the best option because it doesn't clog up code in routes or policies for a single instance.
Write controller/action and add to config/routes.js - While this works, it defeats the purpose of using blueprints, and you have to do all the code in option 1 plus making your routes code messier.
Apply a policy for a single create action - To do this, you will not only have to clutter up your /policies folder but also your policies.js file. This is more code AND it puts the logic for one controller method in two separate places, neither of which is the controller.
Out of these three, option 1 is the best because it contains the code to its context (the controller) and minimizes written code. This is assuming that you only want to alter the create method for one model.
Side note:
As an example of how your use case determines your implementation, here is how I've set my Sails app up which has a few REST routes, each of which responds differently based on the user calling the route (with a valid Facebook token):
Only uses blueprint action routes (not blueprint REST routes)
First goes through the policies:
'*': ['hasFBToken', 'isTokenForMyApp', 'extractFBUser', 'extractMyAppUser'] which store fbuser, myappuser, and other variables in req.body
At this point, my controller function (e.g. controller.action()) is called and can access information about that user and determine if it has the correct permissions to do CRUD.
Pros of this system:
All code for each CRUD operation is contained within its controller function
Minimal to no code in routes.js (no code) or policies.js (one line) or /policies
Works well with API Versioning (e.g. controllers/v1.0/controller.js), which is much easier to do with versioned controllers than versioned models. That means that I can create a new API version and simply by creating a controller in /v2.0 with a function action(), calls such as POST /v2.0/controller/action will exist with no extra routing needed.
Hopefully this example helps illustrate how design decisions were made to offer functionality like API versioning and consolidate code in its specific context.

How do Meteor.subscribe and MyCollection.find* operations interact?

I've been following lots of meteor examples and working through discover meteor, and now I'm left with lots of questions. I understand subscribe and fetch are ways to get "reactivity" to work properly, but I still feel unsure about the relationship between find operations and subscriptions/fetch. I'll try to ask some questions in order to probe for some holistic/conceptual answers.
Question Set 1:
In the following example we are fetching 1 object and we are subscribing to changes on it:
Meteor.subscribe('mycollection', someID);
Mycollection.findOne(someID);
Does order of operations matter here?
When does this subscription "expire"?
Question Set 2:
In some cases we want to foreign key lookup and use fetch like this:
MyCollection2.find({myCollection1Id: doc1Id}).fetch();
Do we need also need a MyColletion2.subscribe when using fetch?
How does subscribe work with "foreign keys"?
Is fetch ~= to a subscription?
Question Set 3:
What is an appropriate use of Tracker.autorun?
Why/when should I use it instead of subscribe or fetch?
what happens when you subscribe and find/fetch
The client calls subscribe which informs the server that the client wants to see a particular set of documents.
The server accepts or rejects the subscription request and publishes the matching set of documents.
Sometime later (after network delay) the documents arrive on the client. They are stored in a database in the browser called minimongo.
A subsequent fetch/find on the collection in which the aforementioned documents are stored will query minimongo (not the server).
If the subscribed document set changes, the server will publish a new copy to the client.
Recommended reading: understanding meteor publications and subscriptions.
question 1
The order matters. You can't see documents that you haven't subscribed for (assuming autopublish is off). However, as I point out in common mistakes, subscriptions don't block. So a subscription followed by an immediate fetch is should return undefined.
Subscriptions don't stop on their own. Here's the breakdown:
A global subscription (one made outside of your router or template) will never stop until you call its stop method.
A route subscription (iron router) will stop when the route changes (with a few caveats).
A template subscription will stop when the template is destroyed.
question 2
This should be mostly explained by the first section of my answer. You'll need both sets of documents in order to join them on the client. You may publish both sets at once from the server, or individually - this is a complex topic and depends on your use case.
question 3
These two are somewhat orthogonal. An autorun is a way for you to create a reactive computation (a function which runs whenever its reactive variables change) - see the section on reactivity from the docs. A find/fetch or a subscribe could happen inside of an autorun depending on your use case. This probably will become more clear once you learn more about how meteor works.
Essentially, when you subscribe to a dataset, it fills minimongo with that data, which is stored in the window's local storage. This is what populates the client's instance of that Mongo with data, otherwise, basically all queries will return undefined data or empty lists.
To summarize: Subscribe and Publish are used to give different data to different users. The most common example would be giving different data based on roles. Say, for instance, you have a web application where you can see a "public" and a "friend" profile.
Meteor.publish('user_profile', function (userId) {
if (Roles.userIsInRole(this.userId, 'can-view', userId)) {
return Meteor.users.find(userId, {
fields: {
public: 1,
profile: 1,
friends: 1,
interests: 1
}
});
} else {
return Meteor.users.find(userId, {
fields: { public: 1 }
});
}
});
Now if you logged in as a user who was not friends with this user, and did Meteor.subscribe('user_profile', 'theidofuser'), and did Meteor.users.findOne(), you would only see their public profile. If you added yourself to the can-view role of the user group, you would be able to see public, profile, friends, and interests. It's essentially for security.
Knowing that, here's how the answers to your questions breaks down:
Order of operations matters, in the sense that you will get undefined unless it's in a reactive block (like Tracker.autorun or Template.helpers).
you still need to use the subscribe when using fetch. All fetch really does is return an array instead of a cursor. To publish with foreign keys is a pretty advanced problem at times, I recommend using reywood:publish-composite, which handles the annoying details for you
Tracker.autorun watches reactive variables within the block and will rerun the function when one of them changes. You don't really ever use it instead of subscribing, you just use it to watch the variables in your scope.

Angular services decoupling

I'm playing with angularjs doing some simple stuff. I wanted to incorporate some real time messaging like PubNub, PubSub or Pusher.
Basically I have it working, but it seems too cumbersome in some areas. Maybe you can show me a better way.
Ok, so here is my working jsfiddle (with pubsub faked) http://jsfiddle.net/canree/dD5VR/
I have external service (pubsub) and created angular service for it.
Now as I don't want other parts of my app to be dependent on this concrete implementation , I decided to create another one (inbox) that would store all the messages pubsub sent. And this inbox would be the dependency across my app (instead of pubsub).
So I want my dependencies to be:
controller -> inbox -> pubsub
When pubsub receives a message it needs to add it to inbox so that controller can see it and react accordingly. The problem is I don't want this backward dependency (pubsub -> inbox). I'd like pubsub to notify observers that message was received.
What I have now is ugly callback registration (inbox registers callback in pubsub). I don't like it very much as it has a lot of boilerplate (regarding this handling etc).
I wonder if there is other way to connect those services? Events (but without being forced to use $rootScope everywhere)? If so, where to register such event handler? Can services receive events?
Take a look at the fiddle provided and advice me how to solve it better way.
I don't want other parts of my app to be dependent on this concrete implementation
The interaction with the server is already wrapped/abstracted in the pubsub service. If the interaction with the server needs to change, it is already isolated to that service. I don't think adding another layer of abstraction buys you enough to offset the extra code you need to write (as you've already seen).
Here are a few other observations about how you could simplify your code (some of this you probably already know, but it might be instructive for others):
The content() function is not necessary (especially since messages is not private data). In Angular the controller $scope/model is often directly tied to a service model, so you could simply use:
$scope.messages = inbox.messages;
(If you like the content() function, consider making messages private in the inbox service.)
There is no need to send an event to the inbox controller about the change to inbox's messages array. The append() function just needs to call $rootScope.$digest(). And actually, this is probably not needed, since the real service would likely be using $http or $resource, which will automatically call $apply() or $digest. So _broadcastNewMessage() is not needed, nor is the $scope.$on() in the controller.
Even if you leave the content() function as is, you still won't need to broadcast anything.
Since inbox is dependent on pubsub, I would have the inbox service register itself
pubsub.subscribe(this.append, this);
pubsub's init() method can be in-line (no function required)
Fiddle with all of the above observations incorporated.
I wonder if there is other way to connect those services?
I can only think of model sharing (e.g., in service1: this.service1_obj_or_obj_property = service2.service2_obj_or_obj_property) and creating functions on the dependent service that the other service can call.
Can services receive events?
Since $on and $broadcast are defined on Scope, and since services are singletons and are not affected by scopes, I'm pretty sure the answer is "no."

Categories