Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

CoffeeScript: Inline call delegation which works with function bindings

I have following CS code snippet:

class Ctrl
    constructor: (@security) ->
        ...

    isAuthenticated: -> @security.isAuthenticated()

which is translated to following JS:

Ctrl = (function() {
    function Ctrl(security) {
        this.security = security;
        ...
    }

    Ctrl.prototype.isAuthenticated = function() {
        return this.security.isAuthenticated();
    };
})()

As you can see isAuthenticated is a simple delegation to security object's method and creating anonymous function is redundant.
I want to avoid creating this additional call level and instead perform kind of 'inline delegation' which would translate to JS similar to:

Ctrl = (function() {
    function Ctrl(security) {
        this.security = security;
        ...
    }

    Ctrl.prototype.isAuthenticated = this.security.isAuthenticated;
})()

Following doesn't work, since it tries to bind @security to wrong object:

class Ctrl
    constructor: (@security) ->
        ...

    isAuthenticated: @security.isAuthenticated

Any clues ?

like image 801
vucalur Avatar asked Mar 16 '14 19:03

vucalur


2 Answers

You can hook up delegation in various ways but you have to be aware of two things:

  1. What @ is when you're hooking up the delegation.
  2. @security.isAuthenticated probably won't work if isAuthenticated is called with a this that isn't @security.

(1) tells you what you need to attach the delegate functions to (see below). (2) is the usual "a function reference isn't really a method" problem in JavaScript; for example:

o =
    m: -> console.log(@)
o.m()        # Puts `o` in the console
f = o.m; f() # Puts `window` (usually) in the console.

So you have to keep isAuthenticated bound to @security or it probably won't work.

One simple way to add delegation is to attach a bunch of bound functions to @ inside the constructor:

delegate = (to, from, methods...) ->
    for m in methods
        to[m] = from[m].bind(from)

class Ctrl
    constructor: (@security) ->
        delegate(@, @security, 'isAuthenticated', 'somethingElse')

Then you can say:

c = new Ctrl(s)
c.isAuthenticated()
c.somethingElse(11)

and the expected things happen. Demo: http://jsfiddle.net/ambiguous/we8gT/

One problem with that approach is that each instance of Ctrl ends up with its own isAuthenticated and somethingElse functions: they're not attached to the prototype so they're not shared.

But we can always create our own functions and take advantage of the fact that we can call code inside class C:

delegate = (klass, property, methods...) ->
    for m in methods
        do (m) -> klass::[m] = (args...) -> @[property][m](args...)

class Ctrl
    delegate(@, 'security', 'isAuthenticated', 'somethingElse')
    constructor: (@security) ->

A few things of note:

  1. :: is used to access the prototype so the methods will be shared.
  2. We use (args...) -> to pass any arguments through to the @security method.
  3. @ at the class level is the class itself.
  4. We use do to ensure that m is what we expect it to be when the delegated function is called.

Demo: http://jsfiddle.net/ambiguous/4c87J/

You can do delegation but you don't get it for free.

like image 170
mu is too short Avatar answered Sep 22 '22 03:09

mu is too short


You cannot use an instance's .security object on the prototype. You need to create the isAuthenticated method in the constructor where you have access to it:

class Ctrl
    constructor: (@security) ->
        @isAuthenticated = security.isAuthenticated
        …

(translate)

like image 38
Bergi Avatar answered Sep 23 '22 03:09

Bergi