How exclude files from the module loader sails.js uses internally - sails.js

I'm using sails.js with a module i created called def-inc to get some sort of inheritance into controllers and models via mixins. Now i want to store the mixins/traits in a traits folder inside models and controllers. I don't want to pollute the api root with another folder to hold my traits, so the ponint is that if it is possible to exclude a folder or a file, with out having to modify the core files?, or at least a way to override the module-loader and configure it to do this.
This is an example of the path structure i want to use, but without getting extra models/controllers.
.
|-- api
| |-- models
| | |-- traits
| | | |-- accountTraits.js
| | |-- User.coffee
| |-- controllers
| | |-- traits
| | | |-- restfullTraits.js
| | |-- UserController.js
Right now if i do that, i get an extra model called accountTraits (and a table if using mysql adapter).
I've checked the code and documentation, and so far this doesn't seem to be supported atm, but since probably it is a regular pattern (outside sails, rails, laravel, etc) to use other objects that are part of the model domain, but aren't specific db models, i assume that someone have done something similar.
Note: I know that for simplicity i can just move the traits folder to the api root path, and i don't consider traits to be part of services, so please avoid answering that, if it isn't possible, just comment my question.
EDIT:
Based on the code provided by #sgress454, i created this code, just to support loadModels too (Which works in the same way), and have a single fn to modify in case i want to apply the same behavior to other moduleLoader methods. Anyways, i'll leave it here just in case somebody needs it (But be sure to upvote #sgress454 :)
var liftOptions = rc('sails');
// Create the module loader override function
liftOptions.moduleLoaderOverride = function(sails, base) {
// Get a reference to the base loaders methods we want to extend
var baseLoadController = base.loadControllers;
var baseLoadModels = base.loadModels;
// Reusable fn to remove modules that match the defined pattern
var removeTraitsFromAutoLoadModules = function(cb, err, modules){
// Remove all modules whose identity ends with "traits"
modules = _.omit(modules, function(module, identity) {
return identity.match(/traits$/);
});
// Return the rest
return cb(err, modules);
};
return {
loadControllers: function (cb) {
baseLoadController(removeTraitsFromAutoLoadModules.bind(null, cb));
},
loadModels: function(cb) {
baseLoadModels(removeTraitsFromAutoLoadModules.bind(null, cb));
}
};
};
// Start server
sails.lift(liftOptions);

You can override the module loader by passing a moduleLoaderOverride function as an option to sails.lift. The function takes two arguments--a reference to the Sails instance, and an object containing the original module loader methods so that you can still call them. The function should return an object containing methods of the module loader that you'd like to override. For example:
// bottom of app.js
// Get the lift options from the .sailsrc file
var liftOptions = rc('sails');
// Include lodash (you may have to npm install it), or else rewrite
// below without the _.omit call
var _ = require('lodash');
// Create the module loader override function
liftOptions.moduleLoaderOverride = function(sails, base) {
// Get a reference to the base loadControllers method we want to extend
var baseLoadControllers = base.loadControllers;
return {
loadControllers: function (cb) {
// Load all of the controllers
baseLoadControllers(function(err, controllers) {
// Remove all controllers whose identity starts with "traits"
controllers = _.omit(controllers, function(controller, identity) {return identity.match(/^traits/);});
// Return the rest
return cb(err, controllers);
});
}
};
};
// Lift Sails
sails.lift(liftOptions);
You'll have to lift your app with node app.js for this to work--there's no way to put this in a regular configuration file and use sails lift, since those are loaded by the module loader!

Related

Can I use in Google Apps Scripts a defined Class in a library with ES6 (V8)?

I'm trying to use a class defined in a library but I only receive an error as a result.
[LibraryProject]/library/model/Update.gs
class Update {
constructor(obj = {}) {
if(typeof obj == "string"){
options = JSON.parse(obj);
}
Object.assign(this, obj);
}
text(){
return (this.message && this.message.text)?this.message.text:''
}
}
TASKS
✅ Create a new version of the project. (File > Manage versions...)
✅ Load this library in another project [Alias: CustomService] (Resources > Libraries...)
✅ Use functions of CustomService
❌ Use class of CustomService
If I try to use a Class
[NormalProject]/index.gs
function test (){
Logger.log(CustomService.libraryFunction())
var update = new CustomService.Update("");
Logger.log(update)
}
TypeError: CustomService.Update is not a constructor (línea 3, archivo "Code")
How can I instantiate an Object of this Class?
If I run...
Logger
As written in the official documentation,
Only the following properties in the script are available to library users:
enumerable global properties
function declarations,
variables created outside a function with var, and
properties explicitly set on the global object.
This would mean every property in the global this object are available to library users.
Before ES6, All declarations outside a function (and function declaration themselves) were properties of this global object. After ES6, There are two kinds of global records:
Object record- Same as ES5.
Function declarations
Function generators
Variable assignments
Declarative record - New
Everything else - let, const, class
Those in the declarative record are not accessible from the global "object", though they are globals themselves. Thus, the class declaration in the library is not accessible to library users. You could simply add a variable assignment to the class to add a property to the global object(outside any function):
var Update = class Update{/*your code here*/}
References:
Library official documentation
Global environment records
Related Answers:
ES6- What about introspection
Do let statements create properties on the global object
Based on your tests, it appears that you cannot directly import a class from a GAS library. I'd recommend creating a factory method to instantiate the class instead.
Something along these lines:
// Library GAS project
/**
* Foo class
*/
class Foo {
constructor(params) {...}
bar() {...}
}
/* globally accessible factory method */
function createFoo(fooParams) {
return new Foo(fooParams);
}
// Client GAS project
function test() {
var foo = FooService.createFoo(fooParams);
Logger.log(foo.bar());
}

Vue2 - Importing Classes, Access and Instantiation

By importing classes at two places do I create 2 different instances?
* content of "MyClass.js"
class MyClass {
constructor() {}
isAuthenticated() {}
}
const cls = new MyClass();
export default cls;
--------------------------------
* content of "router.js"
import auth from "./MyClass";
Vue.use(Router)
--------------------------------
content of "./plugins/MyPlugin.js"
import clsInstance from "./MyClass";
export default {
install(Vue) {
Vue.prototype.$auth = clsInstance;
}
}
--------------------------------
* content of main.js
import myFirstPlugin from "./plugins/MyPlugin.js";
Vue.use(myFirstPlugin);
router.beforeEach((to, from, next) => {
if( auth.isAuthenticated() ){}
}
new Vue({
router
})
--------------------------------
* content of someComponent.vue
methods: {
logOut() {
this.$auth.isAuthenticated()
}
}
Is "auth.isAuthenticated" inside of "router.beforeEach" in "main.js"
identical
with
this.$auth.isAuthenticated() inside of "logOut" in "someComponent.vue"
or there are actually two different instances of "MyClass"created?
import is much the same as require. The code in MyClass.js will only be run once, creating a single instance of MyClass. Both calls to import will be pulling in the same instance.
You can confirm this by:
Putting some console logging in MyClass.js. Note that it only gets run once no matter how many times you import it.
Add a property to the object you import in one file (e.g. set auth.myFlag = true) and then check whether that flag is also present in the other file (i.e. check clsInstance.myFlag). This isn't totally conclusive but it's a pretty good way to verify that it's the same object rather than two separate instances.
If you wanted separate instances you might want to try exporting the class itself so that each file can create its own instance.
From the code you've posted I believe auth.isAuthenticated() and this.$auth.isAuthenticated() are calling the same method on the same object and (depending on what isAuthenticated does) should give the same result.

How to extend JavaScript HTMLElement class in ReasonML for web component?

For the following JavaScript code, how can I write it in ReasonML?
class HelloWorld extends HTMLElement {
constructor() {
super();
// Attach a shadow root to the element.
let shadowRoot = this.attachShadow({mode: 'open'});
shadowRoot.innerHTML = `<p>hello world</p>`;
}
}
I could not find any documentation on writing classes in ReasonML? I cannot use plain objects/types as I need to extend from HTMLElement class which doesn't work with ES style classes.
I have looked into this existing question - How to extend JS class in ReasonML however, it is a different thing. To write web component, we need to extend HTMLElement and must call it with new keyword. ES5 style extension mechanism doesn't work.
You can't. Not directly at least, since BuckleScript (which Reason uses to compile to JavaScript) targets ES5 and therefore has no knowledge of ES6 classes.
Fortunately, ES6-classes require no special runtime support, but are implemented as just syntax sugar, which is why you can transpile ES6 to ES5 as shown in the question you link to. All you really have to do then, is to convert this transpiled output into ReasonML:
var __extends = (this && this.__extends) || function (d, b) {
for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
var BaseElement = (function (_super) {
__extends(BaseElement, _super);
function BaseElement() {
_super.call(this);
}
return BaseElement;
}(HTMLElement));
And depending on what specific class-features you actually need, you can probably simplify it a bit.

Coffeescript "#" variables

What does it mean in Coffeescript when a variable name begins with an "#" sign?
For example, I've been looking through the hubot source code and just in the first few lines I've looked at, I found
class Brain extends EventEmitter
# Represents somewhat persistent storage for the robot. Extend this.
#
# Returns a new Brain with no external storage.
constructor: (robot) ->
#data =
users: { }
_private: { }
#autoSave = true
robot.on "running", =>
#resetSaveInterval 5
I've seen it several other places, but I haven't been able to guess what it means.
The # symbol is a shorcut for this as you can see in Operators and Aliases.
As a shortcut for this.property, you can use #property.
It basically means that the “#” variables are instance variables of the class, that is, class members. Which souldn't be confused with class variables, that you can compare to static members.
Also, you can think of #variables as the this or self operators of OOP languages, but it's not the exact same thing as the old javascript this. That javascript this refer to the current scope, which causes some problems when your are trying to refer to the class scope inside a callback for example, that's why coffescript have introduced the #variables, to solve this kind of problem.
For example, consider the following code:
Brain.prototype = new EventEmitter();
function Brain(robot){
// Represents somewhat persistent storage for the robot. Extend this.
//
// Returns a new Brain with no external storage.
this.data = {
users: { },
_private: { }
};
this.autoSave = true;
var self = this;
robot.on('running', fucntion myCallback() {
// here is the problem, if you try to call `this` here
// it will refer to the `myCallback` instead of the parent
// this.resetSaveInterval(5);
// therefore you have to use the cached `self` way
// which coffeescript solved using #variables
self.resetSaveInterval(5);
});
}
Final thought, the # these days means that you are referring to the class instance (i.e., this or self). So, #data basically means this.data, so, without the #, it would refer to any visible variable data on scope.

What is the most elegant way to print breadcrumbs on a website?

I've been thinking about this for a long time already. What is the best, or most elegant, way of creating breadcrumbs?
One way would be to have a method be called from controllers. Something like this:
function showUsers()
{
$this->breadcrumbs->add('Users', URL::route('admin.users'));
// ...
}
I personally don't like this way, though, because I'd like to separate the breadcrumb-logic from controllers. Also, if I'd like to e.g. have a Admin Panel -item for many pages inside a controller, I'd need to either define it in the constructor or in every controller method.
Another way could be to utilize named routes, by breaking them into segments. This obviously requires that the routes are sensibly named and structured in some way. Here's some pseudocode:
if($segment[0] == 'admin') {
$breadcrumbs->add('Admin Panel', URL::route('admin');
if($segment[1] == 'users') {
$breadcrumbs->add('Users', URL::route('admin.users');
} elseif($segment[1] == 'foo') {
$breadcrumbs->add(...);
}
}
One issue with this approach is that it's hard to get "data" regarding the current route. For example, I can have a route for showing a single user (admin.users.single), which gets a user ID in the route (e.g. admin/users/{id}). There is no native way for me to construct the correct URL for the breadcrumb item, as the route data is only used inside the controller.
Can you think of any other ways? Opinions on these examples?
To make it more generic instead of having conditional statements on users and user names etc. in the controller. I think it would be best to first define some kind of hierarchy or tree of categories. Storing that info on your backend is up to you (i.e. db, text file, nosql):
+--------------+--------------+---------------------+------------+
| CategoryId | CategoryName | CategoryDisplayName | ParentId |
+--------------+--------------+---------------------+------------+
| 1 | admin | Admin Panel | NULL |
+--------------+--------------+---------------------+------------+
| 2 | admin.users | Users | 1 |
+--------------+--------------+---------------------+------------+
You can separate out the logic of traversing the categories and building of the breadcrumb string. This is easier to understand, test, etc.
i.e. with c#
You can retrieve the list of breadcrumbs based on that data:
IEnumerable<Category> GetHierarchy(string categoryName) {
...
}
i.e. GetHierarchy("Users") would return list "Admin Panel", "Users"
Then build the breadcrumb string:
var currentPage = "admin.users"
strings.Join(">", GetHierarchy(currentPage)
.Select(c => GetLink(c.categoryName, c.categoryDisplayName)))
You can embed this into the view with helpers or just set it from the controller.
GetLink method would return the link containing the route for the category.
i.e.
Users