CoffeeScript: Inline call delegation which works with function bindings
Asked Answered
C

3

6

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 ?

Cambrel answered 16/3, 2014 at 19:21 Comment(0)
P
2

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.

Participation answered 17/3, 2014 at 0:50 Comment(0)
E
1

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)

Engineering answered 16/3, 2014 at 20:17 Comment(2)
The problem is that it's not being attached to prototype, as @muIsTooShort mentioned :/Cambrel
No, that's not the problem but the solution! As I tried to explain, it's impossible to attach an instance-specific security method to the prototype. Either use my solution, or the "redundant" anonymous function on the prototype. That is basically what @muIsTooShort's delegate functions do in a generic manner.Engineering
H
1
Ctrl = (function() {
    function Ctrl(security) {
        this.security = security;
        ...
    }

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

would not even work in Javascript.

security would be undefined,and you'd have context (this) issues with isAuthenticated method anyway. You cant save a few strokes on that one.

EDIT :

PROOF : http://jsfiddle.net/camus/Ecd7K/

shows an error on the console

Homes answered 16/3, 2014 at 20:17 Comment(4)
I tried it (changed the resulting JS translation with browser dev tools) and it works. Perhaps because whole Ctrl assignment is passed as an argument of another function. I shown only a snippet of a real code in this thread.Cambrel
Well if it would work, then the coffeescript version would work too, which is obviously not the case since you are asking the question. The piece of code you wrote doesnt work, period , this.security.isAuthenticated doesnt exist in the scope where you are using it : PROOF : jsfiddle.net/camus/Ecd7KHomes
This works for me: plnkr.co/edit/gY3qfZUQ1FDpqyPWpC9E. Perhaps it's because of differences in JS support on Plunker/JSFiddle. Please don't get me wrong: Starting little bit of "Nah, you're not right" was least of my intentions - I'm just a curious geek.Cambrel
You are cheating , this refers to window in Plunker(ie no proper sandboxing),not the Ctrl closure. so this.security is global! If I protect the scope properly with a closure (no globals) : plnkr.co/edit/3Iz3frjtoGFuIJieaEQt?p=preview Doesnt work.Homes

© 2022 - 2024 — McMap. All rights reserved.