How to implement an observer with coffeescript - coffeescript

I try to implement the observer pattern in coffeescript like wikipedia observer pattern. How can this be done with coffeescript?

Create a class like this:
class Observer
constructor: () ->
#subscribers = {}
notify: (to, arg...) ->
if #subscribers[to]
for cb in #subscribers[to].callback
try
cb.apply #,[to].concat arg
catch err
console.log to+" "+(arg?.join ', ')+": "+err
subscribe: (to, cb) ->
if #subscribers[to]==undefined || #subscribers[to].callback.size==0
#subscribers[to]={'callback':[cb]}
else
#subscribers[to].callback.push cb if cb not in #subscribers[to].callback
unsubscribe: (to, cb) ->
#subscribers[to]?.callback.splice #subscribers[to].callback.indexOf(cb), 1
How to use
### Define subscriber ###
class SubscriberA
onEvent: (item) ->
console.log item
class SubscriberB
onEvent: (item) ->
console.log item
class SubscriberC
onEventTypeA: (item) ->
console.log "EventTypeA: "+item
onEventTypeB: (item) ->
console.log "EventTypeB: "+item
class SubscriberD
onEventTypeA: (item,arg="23 is default") ->
console.log item+" "+arg
onEventTypeB: (item,arg...) ->
console.log item + " with: "
for a in arg
console.log a
### Create observer ###
myO = new Observer()
### Create subscribing objects
In real live this would be your objects that will have to provide a function that handels notifications. Within this example they are called onEventXYZ
###
subscriptionOfA = new SubscriberA()
subscriptionOfB = new SubscriberB()
subscriptionOfC = new SubscriberC()
subscriptionOfD = new SubscriberD()
console.log ": Subscribe Peter once and Paul twice"
myO.subscribe "Peter", subscriptionOfA.onEvent
myO.subscribe "Paul", subscriptionOfA.onEvent
myO.subscribe "Paul", subscriptionOfA.onEvent
console.log ": Notify Peter and Paul"
myO.notify "Peter"
myO.notify "Paul"
console.log ": Paul is only notified once as duplicate subscriptions are ignored while subscribe"
console.log ": Subscribe Paul with a different subscriber"
myO.subscribe "Paul", subscriptionOfB.onEvent
console.log ": Notify Peter and Paul"
myO.notify "Peter"
myO.notify "Paul"
console.log ": Paul is notified twice as two different subscriptions are subscribed"
console.log "\n: Unsubscribe Peter"
myO.unsubscribe "Peter"
console.log ": Notify Peter and Paul"
myO.notify "Peter"
myO.notify "Paul"
console.log "\n: Not subscribed items will be ignored at notify"
console.log ": Notify Bob"
myO.notify "Bob"
console.log "\n: You may use multiple eventHandler in your subsribers"
myO.subscribe "Mary", subscriptionOfC.onEventTypeA
myO.subscribe "Jane", subscriptionOfC.onEventTypeB
console.log ": Notify Mary and Jane"
myO.notify "Mary"
myO.notify "Jane"
console.log "\n: You may use arguments with your eventHandler"
myO.subscribe "Laurel", subscriptionOfD.onEventTypeA
myO.subscribe "Hardy", subscriptionOfD.onEventTypeB
console.log ": Notify Laurel without argument"
myO.notify "Laurel"
console.log ": Notify Laurel with argument"
myO.notify "Laurel", 42
console.log ": Notify Hardy with multiple arguments"
myO.notify "Hardy", "Argument String",1,2,3,['a','b']

Related

getting warning in erlang about node names on function restart

I am following the book "Learn me some Erlang". I have installed eclipse on my windows machine in order to run the scripts, and I came across an error on runtime:
** exception error: bad argument
Apparently this error is caused by nodes having the same name on repeated restarts within the judge2 function, third line.
However, I am not sure how to correct the problem.
Here is my code:
start_critic2() ->
spawn(?MODULE, restarter, []).
restarter() ->
process_flag(trap_exit, true),
Pid = spawn_link(?MODULE, critic, []),
receive
{'EXIT', Pid, normal} -> % not a crash
ok;
{'EXIT', Pid, shutdown} -> % manual termination, not a crash
ok;
{'EXIT', Pid, _} ->
restarter()
end.
judge2(Band, Album) ->
Ref = make_ref(),
critic ! {self(), Ref, {Band, Album}},
receive
{Ref, Criticism} -> Criticism
after 2000 ->
timeout
end.
critic2() ->
receive
{From, Ref, {"Rage Against the Turing Machine", "Unit Testify"}} ->
From ! {Ref, "They are great!"};
{From, Ref, {"System of a Downtime", "Memoize"}} ->
From ! {Ref, "They're not Johnny Crash but they're good."};
{From, Ref, {"Johnny Crash", "The Token Ring of Fire"}} ->
From ! {Ref, "Simply incredible."};
{From, Ref, {_Band, _Album}} ->
From ! {Ref, "They are terrible!"}
end,
critic2().

Coffeescript member is undefined

I get the following error:
Uncaught TypeError: Cannot call method 'push' of undefined
In the next code:
class classDemo
names : ['t1', 't2']
methodM1: () ->
# This works:
#names.push 't3'
console.log #names.toString()
#socket = io.connect()
#socket.on 'connect', () ->
# This raise the error:
#names.push 't4'
console.log #names.toString()
Does anyone know how to push into "names" inside the socket.on method? (How to push 't4' correctly?
Thanks
EDIT: The solution proposed by #Sven works for one level of chaining. It seems to fail for two chained calls. Please consider the following example:
methodM1: () ->
_this = #
#socket = io.connect() # connect with no args does auto-discovery
#socket.on 'connect', () ->
# This works:
_this.names.push 'inside connect'
console.log _this.names.toString()
#socket.emit 'getModels', (data) ->
# This does not work:
_this.names.push 'inside emit'
console.log _this.names.toString()
I tried to apply the same solution again inside connect and before emit (see below) but I get no output:
_this2 = _this
#socket.emit 'getModels', (data) ->
_this2.names.push "inside emit"
console.log _this2.names.toString()
Thanks.
your emit is never fired because emit sends data and requieres therefore a datastructure.
Please change your code like this
a) use the fat arrow
b) emit a data structure
methodM1: ->
#socket = io.connect()
#here use the fat arrow it does the '_this = # automatically'
#socket.on 'connect', =>
#names.push 'inside connect'
console.log _this.names.toString()
#socket.emit 'getModels', yo: "got your message"
The fat arrow always binds to the outer instance (see When does the "fat arrow" (=>) bind to "this" instance)
I am not sure (well, I am pretty sure but havent tried it) that you can send a closure over the wire.

Having both object.property and object.property.method() available in coffeescript

[update]
My question may not be clear enough...
Further clarification of what I would like to accomplish :
I retrieve objects like this one:
p =
name:
first: 'alan'
last: 'smith'
and want to create a structure (one class, multiple classes ?) to be able to write things like this ultimately:
person.name # alan smith
person.name.toCap() #Alan Smith
person.name.first # alan
person.name.first.toCap() # Alan
person.name.last # smith
person.name.last.toCap() # Smith
...
so :
Is there a way to have both person.name and person.name.first ?
Is there a better way to extend object properties with methods rather than extending native types like String ?
[original]
Looking for the right way to do this in coffee :
console.log person.name.last #smith
console.log person.name.last.capitalize() # SMITH
console.log person.name.last.initial() # S
I have come up with the following solution, but would like to make sure this is the way to go...
String::toCap = (remainingToLower=false) ->
#[0].toUpperCase() + if remainingToLower then #[1..-1].toLowerCase()
else #[1..-1]
Number::random = (percent) ->
offset = # * percent / 100
parseInt(Math.floor(Math.random() * 2 * offset) + # - offset)
class Name
constructor: (#first, #last) ->
class Person
constructor: (#name, #age) ->
toString: () => "#{#name.first.toCap(true)} #{#name.last.toCap(true)}
(#{#age.random(25)})"
# GO --------------------------->
p = new Person(new Name, 18)
p.name.first = 'alaN'
p.name.last = 'smith'
console.log "#{p.toString()}"
Thanks for your feedback.
Plunk Here
Context
I have this raw data:
data =
name:
first: 'alan'
last: 'smith'
age: 18
address: [
{
name: 'work'
street: '1600 amphitheatre parkway'
city: 'mountain view'
zip: 'CA 94043'
},{
name: 'home'
street: '1 infinite loop'
city: 'cupertino'
zip: 'CA 95014'
}]
And want to create a structure to manipulate my data like this :
p = New Person(data)
console.log """
#{p} # alan smith (18), 2 address lines
#{p.name}, # alan smith
#{p.name.first}, # alan
#{p.address} # 2 address lines
#{p.address.work} # 1600 amphitheatre parkway, mountain view, CA 94043
#{p.address.work.street} # 1600 amphitheatre parkway
"""
Additionally, I want to be able to apply custom methods to any member.
For instance, assuming toCap() is a method that capitalises each word of a String :
console.log """
#{p.name.toCap()}, # Alan Smith
#{p.name.first.toCap()} # Alan
#{p.address.work.toCap()} # 1600 Amphitheatre Parkway, Moutain View, CA 94043
#{p.address.work.street.toCap()} # 1600 Amphitheatre Parkway
"""
Solution (see this Plunk for the full code)
use nested classes
class Person
constructor: (data) ->
#name = new Name(data.name)
#address = new AddressList(data.address)
create members dynamically
class AddressList
constructor: (list) ->
#[addr.name] = new Address(addr) for addr in list
wrap your properties or use base classes rather than extending native objects
class StringProperty
constructor: (#_value) ->
toString: =>
#_value
toCap: (remainingToLower=false) =>
_words = #_value.split ' '
(#_toCap(w,remainingToLower) for w in _words).join ' '
_toCap : (s, r) ->
s[0].toUpperCase() + if r then s[1..-1].toLowerCase() else s[1..-1]
... and use them directly ...
class Name
constructor: (name) ->
#first = new StringProperty(name.first)
#last = new StringProperty(name.last)
toString: =>
"#{#first} #{#last}"
toCap: =>
"#{#first.toCap()} #{#last.toCap()}"
... or create members dynamically :
#[k] = new StringProperty(data[k]) for k of data when k in Address.fields
don't forget to override toString() as above

coffeescript autogenerated namespace causes reference error jasmine test

I try to write some simple tests in Coffeescript and Jasmine.
# greet.coffee
greet = (message, person) ->
"#{message}, #{person}!"
and here my Jasmine Spec File:
# greetSpec.coffee
describe 'greet', ->
it 'should greet with message and name', ->
result = greet 'Hello', 'John'
expect(result).toBe 'Hello, John!'
When i start SpecRunner in Jasmine i get:
ReferenceError: greet is not defined
I guess it has something to do with the namespace autogenerated by coffeescript and therefor is the greet function not visible from the Spec file. How can i solve it?
Ok, could solve it with a simple global variable (not sure if that's a good way, though):
greet.coffee:
#greet = (message, person) ->
"#{message}, #{person}!"
greetSpec.coffee:
describe 'greet', ->
it 'should greet with message and name', ->
result = greet 'Hello', 'John'
expect(result).toBe 'Hello, John!'

How to test a Controller which relies on an ngResource Service

I have a controller which relies on a service built through ngResource. I am having trouble testing this(although both appear to work like a charm in the actual application). The following is the (sanitized) Controller
MyApp.Controller.MyCarsController = (scope, http, typeService) ->
if scope.context==undefined
scope.ferrari_or_porshe =""
scope.id = ""
else if scope.context=="ferrari"
scope.country_or_pi ="Ferrari"
else if scope.context=="porshe"
scope.country_or_pi ="Porshe"
typeService.index
ferrari_or_porshe: scope.ferrari_or_porshe
id: scope.id
, (response) ->
scope.type = response
scope.loading = false
MyApp.Controller.MyCarsController.$inject = ['$scope', '$http', 'Type']
And this is the Service:
MyApp.MyModule.factory 'Type', ['$resource', ($resource) ->
TypeResource = $resource("/api/types/:ferrari_or_porshe/:id", {},
index:
method: "GET"
isArray: true
)
return TypeResource
]
Finally, some test code:
describe 'MyApp.Controller.MyCarsController', ->
beforeEach module('MyModule')
beforeEach inject ($rootScope, $http, $controller, Type) ->
#scope = $rootScope.$new()
#typeService = Type
#scope.context = undefined
$controller 'MyApp.Controller.MyCarsController', $scope: #scope
describe '#home-page', ->
it 'contains a list of types', ->
expect(#scope.types.length).toBeGreaterThan 0
it "sets instance variables correctly", ->
expect(#scope.ferrari_or_porshe).toBe ""
expect(#scope.id).toBe ""
Which fails with:
No more request expected in helpers/angular-mocks.js on line 889
TypeError: 'undefined' is not an object (evaluating 'this.scope.types.length') in controllers/my_cars_controller_spec.js
By judicious application of console.logs, I have discovered that the issue is that the final callback on response is never reached. TypeResource comes back as [Function].
My questions is:
How do I drive the Jasmine Tests to correctly enter the Service and fetch a response? And is there any way to create direct Unit Tests for Services?
Any and all help is appreciated
The Solution is as follows: for the Service, use $httpBackend which is bundled as part of ngMock:
http://code.google.com/p/google-drive-sdk-samples/source/browse/ruby/app/lib/angular-1.0.0/angular-mocks-1.0.0.js?r=c43c943e32be395b7abca8150deb301d3cbc0dbe
Use this to mock the Rest responses. Since in my case I only cared about verifying that a GET request goes out:
describe 'Type', ->
describe '#index', ->
beforeEach module('MyModule')
beforeEach inject(($httpBackend) ->
$httpBackend.whenGET('/api/types/ferrari/1').respond([])
)
it 'for a ferrari scope', inject((Type) ->
ferrari_or_porsche = 'ferrari'
id = '1'
expect( Type.index('ferrari_or_porsche': ferrari_or_porsche, 'id': id) ).toEqual([ ])
)
And then for the controller, mock the service using jasmine spies and use jasmine.any(Function) to warn of the callback.
describe 'MyApp.Controller.MyCarsController', ->
beforeEach module('myModule')
beforeEach inject ($rootScope, $http, $controller, Type) ->
#scope = $rootScope.$new()
#typeService = Type
#scope.context = undefined
spyOn(#typeService, 'index')
describe '#home-page', ->
beforeEach inject ($controller) ->
$controller 'MyApp.Controller.MyCarsController', $scope: #scope
it 'contains a list of types', ->
expect(#typeService.index).toHaveBeenCalledWith({ ferrari_or_porsche : '', id : '' }, jasmine.any(Function))
it "sets instance variables correctly", ->
expect(#scope.ferrari_or_porsche).toBe ""
expect(#scope.id).toBe ""
Note: I make no claims as to the "canonicalness" of this solution. But it works.
Note: The API endpoints are of course tested extensively elsewhere.