0

With someAPI in the following, it requires credentials that I would like to assign dynamically in the constructor. Then I would like to use someAPI throughout the class. I.e. in the following example someMethodUsingSomeAPI is a helper method I would like to call from other methods within an instance of B. Is that possible with Coffee-/JavaScript? (The only way I can get it to work is if I put someMethodUsingSomeAPI inside the constructor.)

SomeAPI = Npm.require 'someAPI'

class B extends A
  constructor: (options = {}) ->
    unless @ instanceof B
      return new B(options)

    @config = JSON.parse(Assets.getText('config/' + options.username + '.json'))

    @someAPI = new SomeAPI
      consumer_key: @config.credentials.consumer.key
      consumer_secret: @config.credentials.consumer.secret
      access_token: @config.credentials.access.token
      access_token_secret: @config.credentials.access.secret

  someMethodUsingSomeAPI = Async.wrap((id, callback) ->
    return @someAPI.get 'whatever/show', { 'id': id }, callback
  )

  console.log someMethodUsingSomeAPI '123' # Error: Cannot call method 'get' of undefined

Updated with suggestion from saimeunt

...

someMethodUsingSomeAPI = (id) ->
  wrappedGet = Async.wrap(@someAPI, 'get')
  wrappedGet 'whatever/show', { id: id }

console.log someMethodUsingSomeAPI '123' # ReferenceError: someMethodUsingSomeAPI is not defined

&

b = B('username')
b.someMethodUsingSomeAPI '123' # Works!

Changing someMethodUsingSomeAPI: to someMethodUsingSomeAPI =

console.log someMethodUsingSomeAPI '123' # Error: unsupported argument list

&

b = B('username')
b.someMethodUsingSomeAPI '123' # TypeError: Object #<B> has no method 'someMethodUsingSomeAPI'

(This is with Meteor 0.9.3.1)

UPDATE IN AN ATTEMPT TO CLARIFY

Here's a simplified version of the above, without any of the API stuff.

someMethod = works, someMethod: doesn't work

I'm happy that classInstance.someMethod works when using :, but would REALLY like to have it work in the actual instance.

jiku
  • 283
  • 5
  • 16
  • Why would you make `someAPI` a static variable outside of your class, instead of an instance *property*? – Bergi Oct 03 '14 at 00:45
  • Notice that `JSON.parse` does take a JSON string, not a file path. – Bergi Oct 03 '14 at 00:45
  • Yeah, sorry about that. Just removed some of it for brevity. Added back. – jiku Oct 03 '14 at 01:02
  • You use `someMethodUsingSomeAPI =` where it should have been `someMethodUsingSomeAPI:`. Then, *instantiate* a `B`, and call the *method* on it. Or why were you defining a class at all? – Bergi Oct 03 '14 at 01:08
  • If I change it from = to :, I get `ReferenceError: someMethodUsingSomeAPI is not defined` with the above trace. If I do `B.someMethodUsingSomeAPI '123'` I get `Cannot call method 'get' of undefined`. I would like to call this helper method from other methods within B. – jiku Oct 03 '14 at 01:15
  • I said you should **create an instance** (`b = new B; b.someMethodUsingSomeAPI(…)`). – Bergi Oct 03 '14 at 10:15
  • What do you mean by "*would REALLY like to have it work in the actual instance.*"? Isn't that just what you have already working? Notice that your `someMethodUsingSomeAPI` depends on `.someAPI` property of a specific instance which is created when *constructing* the instance with a configuration; so you cannot use `someMethodUsingSomeAPI` as a class method. – Bergi Oct 03 '14 at 10:19

3 Answers3

0

Sure, you can attach someAPI onto your class object. In coffeescript @ is used in place of this (as it would be in Javscript).

SomeAPI = require 'someAPI'

class A extends B
  constructor: (options = {}) ->
    unless @ instanceof A
      return new A(options)

    @config = JSON.parse('config/' + options.username + '.json')

    @someAPI = new SomeAPI
      consumer_key: @config.credentials.consumer.key
      consumer_secret: @config.credentials.consumer.secret
      access_token: @config.credentials.access.token
      access_token_secret: @config.credentials.access.secret

  someMethodUsingSomeAPI:  (id, callback) ->
    return @someAPI.get 'whatever/show', { 'id': id }, callback

You can take a look at this SO question which explains the Javascript this, and its scope. Once you understand that, take a look at how coffeescript's classes work, and you should have some clarity on the use of @.

Community
  • 1
  • 1
milkandtang
  • 576
  • 3
  • 6
  • Thanks. I think I tried this to begin with, before trying to declare someAPI outside of the constructor, etc. Updated the original post to reflect what I'm doing, but I still get the same error, i.e. `Cannot call method 'get' of undefined`. – jiku Oct 03 '14 at 01:07
0

I think this is what you want :

someMethodUsingSomeAPI:function(id){
  var wrappedGet=Async.wrap(@someAPI,"get");
  return wrappedGet("whatever/show",{
    id:id
  });
}
saimeunt
  • 22,666
  • 2
  • 56
  • 61
  • That worked great for b.someMethodUsingSomeAPI, but unless I messed up the CS it doesn't work inside the instance. Updated the example above with the results. – jiku Oct 03 '14 at 02:32
  • Please remove the `=` in favor of `:` when defining new properties on an object literal because if you don't it makes absolutely NO SENSE and I don't know how on earth CS is not warning you about this. What about these instanciations of B without `new` ? Did you call someMethodUsingSomeAPI using the proper syntax in other methods, ie `@someMethodUsingSomeAPI` ? – saimeunt Oct 03 '14 at 02:54
  • Ok. I have no idea why but using = I actually had `console.log someMethodUsingSomeAPI '123'` working if I put both inside the constructor. B wit(out) new doesn't seem to make a difference. Just returns a new unless already an instance of B as stated in the constructor. If I do `console.log @someMethodUsingSomeAPI '123'`, I get `TypeError: Object # has no method 'someMethodUsingSomeAPI'` – jiku Oct 03 '14 at 03:05
  • You need to edit your code and remove ellipses to show us what you're actually testing against. Telling us results of console.log is of little help because we don't know the context of execution. – saimeunt Oct 03 '14 at 03:12
  • That's the full code and I'm not sure I can describe the issue any more succinctly than what I have in the introduction. Again, `someMethodUsingSomeAPI` works when called from a same-level method if both are inside the constructor, where `(@)someAPI` seems it has to be both declared and instantiated if it's to use those dynamic credentials. `someMethodUsingSomeAPI` doesn't work when called from a same-level method if they're not in the constructor, since (@)someAPI is undefined, or if declared outside of the constructor, doesn't have a get method. – jiku Oct 03 '14 at 03:25
  • Added some simple examples that run in the browser and exhibit the same behavior. – jiku Oct 03 '14 at 04:08
0

Based on the simplified example you provided here's a version that should comply with your requirements:

class SomeClass
  # Store the last created instance at the class level
  lastInstance: null

  constructor: (options = {}) ->
    @someLastName = options.someLastName 
    # In constructor, the created instance is now the lastInstance
    SomeClass.lastInstance = this

# The helper method is not created in the class scope
someMethod = (someFirstName) ->
  "Hello " + someFirstName + " " + SomeClass.lastInstance.someLastName

someClass = new SomeClass({ someLastName: 'Borgnine' })

alert someMethod 'Ernest'

And here's a link to try it in a browser.

Basically, as you expect your helper method to access an instance of the class, you'll need to offer a reference to that instance the helper can reference at some point. Here I'm using a class variable to store the last created instance, and the helper access that last instance when called. That means that when creating many instances it will perform as follow:

someClass = new SomeClass({ someLastName: 'Borgnine' })

alert someMethod 'Ernest' # Hello Ernest Borgnine

someClass = new SomeClass({ someLastName: 'Doe' })

alert someMethod 'Boris' # Hello Boris Doe