dripdrop 0.3.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.md +23 -28
- data/VERSION +1 -1
- data/dripdrop.gemspec +25 -3
- data/example/combined.rb +33 -0
- data/example/pubsub.rb +7 -15
- data/example/stats_app/core.rb +113 -0
- data/example/stats_app/public/.sass-cache/b48b4299d80c05f528daf63fe51d85e5e3c10d98/stats.scssc +0 -0
- data/example/stats_app/public/backbone.js +16 -0
- data/example/stats_app/public/build_templates.rb +5 -0
- data/example/stats_app/public/json2.js +482 -0
- data/example/stats_app/public/protovis-r3.2.js +277 -0
- data/example/stats_app/public/stats.css +5 -0
- data/example/stats_app/public/stats.haml +61 -0
- data/example/stats_app/public/stats.html +26 -0
- data/example/stats_app/public/stats.js +113 -0
- data/example/stats_app/public/stats.scss +10 -0
- data/example/stats_app/public/underscore.js +17 -0
- data/example/xreq_xrep.rb +9 -11
- data/js/dripdrop.js +6 -2
- data/lib/dripdrop/handlers/base.rb +18 -0
- data/lib/dripdrop/handlers/http.rb +18 -18
- data/lib/dripdrop/handlers/websockets.rb +33 -26
- data/lib/dripdrop/handlers/zeromq.rb +30 -24
- data/lib/dripdrop/message.rb +5 -0
- data/lib/dripdrop/node/nodelet.rb +29 -0
- data/lib/dripdrop/node.rb +103 -25
- data/spec/gimite-websocket.rb +442 -0
- data/spec/message_spec.rb +5 -0
- data/spec/node/http_spec.rb +2 -8
- data/spec/node/nodelet_spec.rb +57 -0
- data/spec/node/routing_spec.rb +68 -0
- data/spec/node/websocket_spec.rb +88 -0
- data/spec/node/zmq_pushpull_spec.rb +2 -6
- data/spec/node/zmq_xrepxreq_spec.rb +24 -24
- data/spec/node_spec.rb +0 -1
- data/spec/spec_helper.rb +17 -3
- metadata +27 -5
- data/js/jack.js +0 -876
data/README.md
CHANGED
@@ -4,48 +4,43 @@
|
|
4
4
|
|
5
5
|
DripDrop is ZeroMQ(using zmqmachine) + Event Machine simplified for the general use case + serialization helpers.
|
6
6
|
|
7
|
-
Here's an example of the kind of thing DripDrop makes easy, from [
|
8
|
-
|
9
|
-
require 'dripdrop
|
10
|
-
Thread.abort_on_exception = true
|
11
|
-
|
12
|
-
#
|
7
|
+
Here's an example of the kind of thing DripDrop makes easy, from [example/combined.rb](http://github.com/andrewvc/dripdrop/blob/master/example/combined.rb)
|
8
|
+
|
9
|
+
require 'dripdrop'
|
10
|
+
Thread.abort_on_exception = true #Always a good idea in multithreaded apps.
|
11
|
+
|
12
|
+
# Encapsulates our EM and ZMQ reactors
|
13
13
|
DripDrop::Node.new do
|
14
|
-
|
14
|
+
# Define all our sockets
|
15
|
+
route :stats_pub, :zmq_publish, 'tcp://127.0.0.1:2200', :bind
|
16
|
+
route :stats_sub1, :zmq_subscribe, stats_pub.address, :connect
|
17
|
+
route :stats_sub2, :zmq_subscribe, stats_pub.address, :connect
|
18
|
+
route :http_collector, :http_server, 'http://127.0.0.1:8080'
|
19
|
+
route :http_agent, :http_client, http_collector.address
|
15
20
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
#Create two subscribers
|
20
|
-
zmq_subscribe(z_addr,:connect).on_recv do |message|
|
21
|
-
puts "Receiver 1 #{message.inspect}"
|
21
|
+
stats_sub1.on_recv do |message|
|
22
|
+
puts "Receiver 1: #{message.body}"
|
22
23
|
end
|
23
|
-
|
24
|
-
puts "Receiver 2 #{message.
|
24
|
+
stats_sub2.on_recv do |message|
|
25
|
+
puts "Receiver 2: #{message.body}"
|
25
26
|
end
|
26
27
|
|
27
|
-
|
28
|
-
|
29
|
-
pub.send_message(:name => 'test', :body => 'Test Payload')
|
30
|
-
end
|
31
|
-
|
32
|
-
http_server(addr).on_recv do |msg,response|
|
28
|
+
i = 0
|
29
|
+
http_collector.on_recv do |message,response|
|
33
30
|
i += 1
|
34
|
-
|
31
|
+
stats_pub.send_message(message)
|
32
|
+
response.send_message(:name => 'ack', :body => {:seq => i})
|
35
33
|
end
|
36
34
|
|
37
35
|
EM::PeriodicTimer.new(1) do
|
38
|
-
client = http_client(addr)
|
39
36
|
msg = DripDrop::Message.new('http/status', :body => "Success #{i}")
|
40
|
-
|
41
|
-
puts resp_msg.
|
37
|
+
http_agent.send_message(msg) do |resp_msg|
|
38
|
+
puts "RESP: #{resp_msg.body['seq']}"
|
42
39
|
end
|
43
40
|
end
|
44
41
|
end.start! #Start the reactor and block until complete
|
45
42
|
|
46
|
-
Note that these aren't regular ZMQ sockets, and that the HTTP server isn't a regular server. They only speak and respond using DripDrop::Message formatted messages. For HTTP/WebSockets it's JSON that looks like {name: 'name', head: {}, body: anything}, for ZeroMQ it means BERT. There is a raw
|
47
|
-
|
48
|
-
Want to see a longer example encapsulating both zmqmachine and eventmachine functionality? Check out [this file](http://github.com/andrewvc/dripdrop-webstats/blob/master/lib/dripdrop-webstats.rb).
|
43
|
+
Note that these aren't regular ZMQ sockets, and that the HTTP server isn't a regular server. They only speak and respond using DripDrop::Message formatted messages. For HTTP/WebSockets it's JSON that looks like {name: 'name', head: {}, body: anything}, for ZeroMQ it means BERT. There is a raw mode that you can use for other message formats, but using DripDrop::Messages makes things easier, and for some socket types (like XREQ/XREP) the predefined format is very useful in matching requests to replies.
|
49
44
|
|
50
45
|
#RDoc
|
51
46
|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.4.0
|
data/dripdrop.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{dripdrop}
|
8
|
-
s.version = "0.
|
8
|
+
s.version = "0.4.0"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Andrew Cholakian"]
|
12
|
-
s.date = %q{2010-
|
12
|
+
s.date = %q{2010-11-15}
|
13
13
|
s.description = %q{Evented framework for ZeroMQ and EventMachine Apps. }
|
14
14
|
s.email = %q{andrew@andrewvc.com}
|
15
15
|
s.extra_rdoc_files = [
|
@@ -26,25 +26,43 @@ Gem::Specification.new do |s|
|
|
26
26
|
"doc_img/topology.png",
|
27
27
|
"dripdrop.gemspec",
|
28
28
|
"example/agent_test.rb",
|
29
|
+
"example/combined.rb",
|
29
30
|
"example/http.rb",
|
30
31
|
"example/pubsub.rb",
|
31
32
|
"example/pushpull.rb",
|
33
|
+
"example/stats_app/core.rb",
|
34
|
+
"example/stats_app/public/.sass-cache/b48b4299d80c05f528daf63fe51d85e5e3c10d98/stats.scssc",
|
35
|
+
"example/stats_app/public/backbone.js",
|
36
|
+
"example/stats_app/public/build_templates.rb",
|
37
|
+
"example/stats_app/public/json2.js",
|
38
|
+
"example/stats_app/public/protovis-r3.2.js",
|
39
|
+
"example/stats_app/public/stats.css",
|
40
|
+
"example/stats_app/public/stats.haml",
|
41
|
+
"example/stats_app/public/stats.html",
|
42
|
+
"example/stats_app/public/stats.js",
|
43
|
+
"example/stats_app/public/stats.scss",
|
44
|
+
"example/stats_app/public/underscore.js",
|
32
45
|
"example/subclass.rb",
|
33
46
|
"example/xreq_xrep.rb",
|
34
47
|
"js/dripdrop.html",
|
35
48
|
"js/dripdrop.js",
|
36
|
-
"js/jack.js",
|
37
49
|
"js/qunit.css",
|
38
50
|
"js/qunit.js",
|
39
51
|
"lib/dripdrop.rb",
|
40
52
|
"lib/dripdrop/agent.rb",
|
53
|
+
"lib/dripdrop/handlers/base.rb",
|
41
54
|
"lib/dripdrop/handlers/http.rb",
|
42
55
|
"lib/dripdrop/handlers/websockets.rb",
|
43
56
|
"lib/dripdrop/handlers/zeromq.rb",
|
44
57
|
"lib/dripdrop/message.rb",
|
45
58
|
"lib/dripdrop/node.rb",
|
59
|
+
"lib/dripdrop/node/nodelet.rb",
|
60
|
+
"spec/gimite-websocket.rb",
|
46
61
|
"spec/message_spec.rb",
|
47
62
|
"spec/node/http_spec.rb",
|
63
|
+
"spec/node/nodelet_spec.rb",
|
64
|
+
"spec/node/routing_spec.rb",
|
65
|
+
"spec/node/websocket_spec.rb",
|
48
66
|
"spec/node/zmq_pushpull_spec.rb",
|
49
67
|
"spec/node/zmq_xrepxreq_spec.rb",
|
50
68
|
"spec/node_spec.rb",
|
@@ -57,9 +75,13 @@ Gem::Specification.new do |s|
|
|
57
75
|
s.summary = %q{Evented framework for ZeroMQ and EventMachine Apps.}
|
58
76
|
s.test_files = [
|
59
77
|
"spec/spec_helper.rb",
|
78
|
+
"spec/gimite-websocket.rb",
|
60
79
|
"spec/node/http_spec.rb",
|
80
|
+
"spec/node/routing_spec.rb",
|
61
81
|
"spec/node/zmq_xrepxreq_spec.rb",
|
62
82
|
"spec/node/zmq_pushpull_spec.rb",
|
83
|
+
"spec/node/nodelet_spec.rb",
|
84
|
+
"spec/node/websocket_spec.rb",
|
63
85
|
"spec/message_spec.rb",
|
64
86
|
"spec/node_spec.rb"
|
65
87
|
]
|
data/example/combined.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'dripdrop'
|
2
|
+
Thread.abort_on_exception = true #Always a good idea in multithreaded apps.
|
3
|
+
|
4
|
+
# Encapsulates our EM and ZMQ reactors
|
5
|
+
DripDrop::Node.new do
|
6
|
+
# Define all our sockets
|
7
|
+
route :stats_pub, :zmq_publish, 'tcp://127.0.0.1:2200', :bind
|
8
|
+
route :stats_sub1, :zmq_subscribe, stats_pub.address, :connect
|
9
|
+
route :stats_sub2, :zmq_subscribe, stats_pub.address, :connect
|
10
|
+
route :http_collector, :http_server, 'http://127.0.0.1:8080'
|
11
|
+
route :http_agent, :http_client, http_collector.address
|
12
|
+
|
13
|
+
stats_sub1.on_recv do |message|
|
14
|
+
puts "Receiver 1: #{message.body}"
|
15
|
+
end
|
16
|
+
stats_sub2.on_recv do |message|
|
17
|
+
puts "Receiver 2: #{message.body}"
|
18
|
+
end
|
19
|
+
|
20
|
+
i = 0
|
21
|
+
http_collector.on_recv do |message,response|
|
22
|
+
i += 1
|
23
|
+
stats_pub.send_message(message)
|
24
|
+
response.send_message(:name => 'ack', :body => {:seq => i})
|
25
|
+
end
|
26
|
+
|
27
|
+
EM::PeriodicTimer.new(1) do
|
28
|
+
msg = DripDrop::Message.new('http/status', :body => "Success #{i}")
|
29
|
+
http_agent.send_message(msg) do |resp_msg|
|
30
|
+
puts "RESP: #{resp_msg.body['seq']}"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end.start! #Start the reactor and block until complete
|
data/example/pubsub.rb
CHANGED
@@ -3,33 +3,25 @@ Thread.abort_on_exception = true
|
|
3
3
|
|
4
4
|
#Define our handlers
|
5
5
|
DripDrop::Node.new do
|
6
|
-
|
6
|
+
route :pub, :zmq_publish, 'tcp://127.0.0.1:2200', :bind
|
7
|
+
route :sub1, :zmq_subscribe, pub.address, :connect, :topic_filter => /[13579]$/
|
8
|
+
route :sub2, :zmq_subscribe, pub.address, :connect, :topic_filter => /[02468]$/
|
9
|
+
route :sub3, :zmq_subscribe, pub.address, :connect
|
7
10
|
|
8
|
-
#Create a publisher
|
9
|
-
pub = zmq_publish(z_addr,:bind)
|
10
|
-
|
11
|
-
#Create three subscribers
|
12
|
-
sub1 = zmq_subscribe(z_addr,:connect)
|
13
|
-
|
14
11
|
sub1.on_recv do |message|
|
15
12
|
puts "Receiver 1 #{message.inspect}"
|
16
13
|
end
|
17
14
|
|
18
|
-
sub1.topic_filter = /[13579]$/
|
19
|
-
|
20
|
-
sub2 = zmq_subscribe(z_addr,:connect)
|
21
|
-
|
22
15
|
sub2.on_recv do |message|
|
23
16
|
puts "Receiver 2 #{message.inspect}"
|
24
17
|
end
|
25
18
|
|
26
|
-
|
27
|
-
|
28
|
-
zmq_subscribe(z_addr, :connect).on_recv do |message|
|
19
|
+
sub3.on_recv do |message|
|
29
20
|
puts "Receiver 3 #{message.inspect}"
|
30
21
|
end
|
31
22
|
|
32
|
-
zm_reactor.periodical_timer(
|
23
|
+
zm_reactor.periodical_timer(500) do
|
24
|
+
puts "Sending!"
|
33
25
|
#Sending a hash as a message implicitly transforms it into a DripDrop::Message
|
34
26
|
pub.send_message(:name => Time.now.to_i.to_s, :body => 'Test Payload')
|
35
27
|
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
require 'dripdrop'
|
2
|
+
Thread.abort_on_exception = true #Always a good idea in multithreaded apps.
|
3
|
+
|
4
|
+
# This demo app is an message stats application
|
5
|
+
# It receives stats data via either HTTP or ZMQ directly, aggregates,
|
6
|
+
# and keeps track of data.
|
7
|
+
DripDrop::Node.new do
|
8
|
+
routes_for :agg do
|
9
|
+
route :input, :zmq_subscribe, 'tcp://127.0.0.1:2200', :bind
|
10
|
+
route :output, :zmq_publish, 'tcp://127.0.0.1:2201', :bind
|
11
|
+
route :input_http, :http_server, 'http://127.0.0.1:8082'
|
12
|
+
end
|
13
|
+
|
14
|
+
routes_for :counter do
|
15
|
+
route :input, :zmq_subscribe, agg_output.address, :connect
|
16
|
+
route :query, :zmq_xrep, 'tcp://127.0.0.1:2203', :bind
|
17
|
+
route :query_http, :http_server, 'tcp://0.0.0.0:8081'
|
18
|
+
end
|
19
|
+
|
20
|
+
routes_for :tracer do
|
21
|
+
route :input, :zmq_subscribe, agg_output.address, :connect, :topic_filter => /^ip_trace_req$/
|
22
|
+
route :output, :zmq_publish, 'tcp://127.0.0.1:2204', :bind
|
23
|
+
end
|
24
|
+
|
25
|
+
routes_for :ws_stream do
|
26
|
+
route :tracer_input, :zmq_subscribe, agg_output.address, :connect
|
27
|
+
route :agg_input, :zmq_subscribe, tracer_output.address, :connect
|
28
|
+
route :client, :websocket, 'ws://127.0.0.1:2202'
|
29
|
+
end
|
30
|
+
|
31
|
+
routes_for :heartbeat do
|
32
|
+
route :output, :zmq_publish, agg_input.address, :connect
|
33
|
+
end
|
34
|
+
|
35
|
+
nodelet :agg do |agg|
|
36
|
+
agg.input.on_recv do |message|
|
37
|
+
agg.output.send_message(message)
|
38
|
+
end
|
39
|
+
|
40
|
+
agg.input.on_recv do |message|
|
41
|
+
agg.output.send_message(message)
|
42
|
+
end
|
43
|
+
|
44
|
+
agg.input_http.on_recv do |message,response,env|
|
45
|
+
response.send_message(:name => 'ack')
|
46
|
+
agg.output.send_message(message)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
nodelet :counter do |cntr|
|
51
|
+
stats = {:total => 0, :name_counts => Hash.new(0) }
|
52
|
+
|
53
|
+
cntr.input.on_recv do |message|
|
54
|
+
stats[:total] += 1
|
55
|
+
stats[:name_counts][message.name] += 1
|
56
|
+
end
|
57
|
+
|
58
|
+
cntr.query.on_recv do |message,ids,seq|
|
59
|
+
cntr.query.send_message({:name => 'stats', :body => @stats}, ids, seq)
|
60
|
+
end
|
61
|
+
|
62
|
+
cntr.query_http.on_recv do |message,response|
|
63
|
+
response.send_message(:name => 'stats', :body => @stats)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
nodelet :tracer do |tracer|
|
68
|
+
tracer_memo = {}
|
69
|
+
|
70
|
+
ip_regexp = /\A(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\Z/
|
71
|
+
tracer.input.on_recv do |message|
|
72
|
+
puts "TRACE #{message.body.inspect}"
|
73
|
+
|
74
|
+
ip = message.body['ip']
|
75
|
+
puts "IP #{message.inspect}"
|
76
|
+
if ip =~ ip_regexp
|
77
|
+
memoized_res = tracer_memo[ip]
|
78
|
+
if memoized_res
|
79
|
+
tracer.output.send_message(:name => 'ip_route', :body => {:ip => ip, :route => memoized_res})
|
80
|
+
else
|
81
|
+
EM.system("/usr/sbin/traceroute -w 4 #{ip}") do |output,status|
|
82
|
+
route = output.split("\n")[1..-1].map {|l| l.split(/ /)[3] }.select {|a| a != '*'}
|
83
|
+
tracer_memo[ip] = route
|
84
|
+
tracer.output.send_message(:name => 'ip_route', :body => {:ip => ip, :route => route})
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
nodelet :ws_stream do |wss|
|
92
|
+
[wss.tracer_input, wss.agg_input].each do |input|
|
93
|
+
input.on_recv do |message|
|
94
|
+
send_internal(:wss, message)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
wss.client.on_open do |ws|
|
99
|
+
recv_internal(:wss, ws.signature) do |message|
|
100
|
+
ws.send_message(message)
|
101
|
+
end
|
102
|
+
end.on_recv do |message,ws|
|
103
|
+
end.on_close do |ws|
|
104
|
+
end.on_error do |ws|
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
nodelet :heartbeat do |hbeat|
|
109
|
+
zm_reactor.periodical_timer(1000) do
|
110
|
+
hbeat.output.send_message(:name => 'heartbeat/tick', :body => Time.now.to_i)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end.start! #Start the reactor and block until complete
|
data/example/stats_app/public/.sass-cache/b48b4299d80c05f528daf63fe51d85e5e3c10d98/stats.scssc
ADDED
Binary file
|
@@ -0,0 +1,16 @@
|
|
1
|
+
(function(){var f;f=typeof exports!=="undefined"?exports:this.Backbone={};f.VERSION="0.2.0";var e=this._;if(!e&&typeof require!=="undefined")e=require("underscore")._;var h=this.jQuery;f.emulateHttp=false;f.Events={bind:function(a,b){this._callbacks||(this._callbacks={});(this._callbacks[a]||(this._callbacks[a]=[])).push(b);return this},unbind:function(a,b){var c;if(a){if(c=this._callbacks)if(b){c=c[a];if(!c)return this;for(var d=0,g=c.length;d<g;d++)if(b===c[d]){c.splice(d,1);break}}else c[a]=[]}else this._callbacks=
|
2
|
+
{};return this},trigger:function(a){var b,c,d,g;if(!(c=this._callbacks))return this;if(b=c[a]){d=0;for(g=b.length;d<g;d++)b[d].apply(this,Array.prototype.slice.call(arguments,1))}if(b=c.all){d=0;for(g=b.length;d<g;d++)b[d].apply(this,arguments)}return this}};f.Model=function(a){this.attributes={};this.cid=e.uniqueId("c");this.set(a||{},{silent:true});this._previousAttributes=e.clone(this.attributes);this.initialize&&this.initialize(a)};e.extend(f.Model.prototype,f.Events,{_previousAttributes:null,
|
3
|
+
_changed:false,toJSON:function(){return e.clone(this.attributes)},get:function(a){return this.attributes[a]},set:function(a,b){b||(b={});if(!a)return this;if(a.attributes)a=a.attributes;var c=this.attributes;if(this.validate){var d=this.validate(a);if(d){b.error?b.error(this,d):this.trigger("error",this,d);return false}}if("id"in a)this.id=a.id;for(var g in a){d=a[g];if(d==="")d=null;if(!e.isEqual(c[g],d)){c[g]=d;if(!b.silent){this._changed=true;this.trigger("change:"+g,this,d)}}}!b.silent&&this._changed&&
|
4
|
+
this.change();return this},unset:function(a,b){b||(b={});var c=this.attributes[a];delete this.attributes[a];if(!b.silent){this._changed=true;this.trigger("change:"+a,this);this.change()}return c},fetch:function(a){a||(a={});var b=this,c=a.error&&e.bind(a.error,null,b);f.sync("read",this,function(d){if(!b.set(b.parse(d),a))return false;a.success&&a.success(b,d)},c);return this},save:function(a,b){a||(a={});b||(b={});if(!this.set(a,b))return false;var c=this,d=b.error&&e.bind(b.error,null,c),g=this.isNew()?
|
5
|
+
"create":"update";f.sync(g,this,function(i){if(!c.set(c.parse(i),b))return false;b.success&&b.success(c,i)},d);return this},destroy:function(a){a||(a={});var b=this,c=a.error&&e.bind(a.error,null,b);f.sync("delete",this,function(d){b.collection&&b.collection.remove(b);a.success&&a.success(b,d)},c);return this},url:function(){var a=j(this.collection);if(this.isNew())return a;return a+"/"+this.id},parse:function(a){return a},clone:function(){return new this.constructor(this)},isNew:function(){return!this.id},
|
6
|
+
change:function(){this.trigger("change",this);this._previousAttributes=e.clone(this.attributes);this._changed=false},hasChanged:function(a){if(a)return this._previousAttributes[a]!=this.attributes[a];return this._changed},changedAttributes:function(a){a||(a=this.attributes);var b=this._previousAttributes,c=false,d;for(d in a)if(!e.isEqual(b[d],a[d])){c=c||{};c[d]=a[d]}return c},previous:function(a){if(!a||!this._previousAttributes)return null;return this._previousAttributes[a]},previousAttributes:function(){return e.clone(this._previousAttributes)}});
|
7
|
+
f.Collection=function(a,b){b||(b={});if(b.comparator){this.comparator=b.comparator;delete b.comparator}this._boundOnModelEvent=e.bind(this._onModelEvent,this);this._reset();a&&this.refresh(a,{silent:true});this.initialize&&this.initialize(a,b)};e.extend(f.Collection.prototype,f.Events,{model:f.Model,toJSON:function(){return this.map(function(a){return a.toJSON()})},add:function(a,b){if(e.isArray(a))for(var c=0,d=a.length;c<d;c++)this._add(a[c],b);else this._add(a,b);return this},remove:function(a,
|
8
|
+
b){if(e.isArray(a))for(var c=0,d=a.length;c<d;c++)this._remove(a[c],b);else this._remove(a,b);return this},get:function(a){return a&&this._byId[a.id!=null?a.id:a]},getByCid:function(a){return a&&this._byCid[a.cid||a]},at:function(a){return this.models[a]},sort:function(a){a||(a={});if(!this.comparator)throw Error("Cannot sort a set without a comparator");this.models=this.sortBy(this.comparator);a.silent||this.trigger("refresh",this);return this},pluck:function(a){return e.map(this.models,function(b){return b.get(a)})},
|
9
|
+
refresh:function(a,b){a||(a=[]);b||(b={});this._reset();this.add(a,{silent:true});b.silent||this.trigger("refresh",this);return this},fetch:function(a){a||(a={});var b=this,c=a.error&&e.bind(a.error,null,b);f.sync("read",this,function(d){b.refresh(b.parse(d));a.success&&a.success(b,d)},c);return this},create:function(a,b){b||(b={});a instanceof f.Model||(a=new this.model(a));var c=a.collection=this;return a.save(null,{success:function(d,g){c.add(d);b.success&&b.success(d,g)},error:b.error})},parse:function(a){return a},
|
10
|
+
chain:function(){return e(this.models).chain()},_reset:function(){this.length=0;this.models=[];this._byId={};this._byCid={}},_add:function(a,b){b||(b={});a instanceof f.Model||(a=new this.model(a));var c=this.getByCid(a);if(c)throw Error(["Can't add the same model to a set twice",c.id]);this._byId[a.id]=a;this._byCid[a.cid]=a;a.collection=this;this.models.splice(this.comparator?this.sortedIndex(a,this.comparator):this.length,0,a);a.bind("all",this._boundOnModelEvent);this.length++;b.silent||this.trigger("add",
|
11
|
+
a);return a},_remove:function(a,b){b||(b={});a=this.getByCid(a);if(!a)return null;delete this._byId[a.id];delete this._byCid[a.cid];delete a.collection;this.models.splice(this.indexOf(a),1);a.unbind("all",this._boundOnModelEvent);this.length--;b.silent||this.trigger("remove",a);return a},_onModelEvent:function(a,b){if(a==="change:id"){delete this._byId[b.previous("id")];this._byId[b.id]=b}this.trigger.apply(this,arguments)}});e.each(["forEach","each","map","reduce","reduceRight","find","detect","filter",
|
12
|
+
"select","reject","every","all","some","any","include","invoke","max","min","sortBy","sortedIndex","toArray","size","first","rest","last","without","indexOf","lastIndexOf","isEmpty"],function(a){f.Collection.prototype[a]=function(){return e[a].apply(e,[this.models].concat(e.toArray(arguments)))}});f.View=function(a){this._configure(a||{});this._ensureElement();this.delegateEvents();this.initialize&&this.initialize(a)};var k=function(a){return h(a,this.el)},l=/^(\w+)\s*(.*)$/;e.extend(f.View.prototype,
|
13
|
+
{tagName:"div",$:k,jQuery:k,render:function(){return this},make:function(a,b,c){a=document.createElement(a);b&&h(a).attr(b);c&&h(a).html(c);return a},delegateEvents:function(a){if(!(a||(a=this.events)))return this;h(this.el).unbind();for(var b in a){var c=a[b],d=b.match(l),g=d[1];d=d[2];c=e.bind(this[c],this);d===""?h(this.el).bind(g,c):h(this.el).delegate(d,g,c)}return this},_configure:function(a){if(this.options)a=e.extend({},this.options,a);if(a.model)this.model=a.model;if(a.collection)this.collection=
|
14
|
+
a.collection;if(a.el)this.el=a.el;if(a.id)this.id=a.id;if(a.className)this.className=a.className;if(a.tagName)this.tagName=a.tagName;this.options=a},_ensureElement:function(){if(!this.el){var a={};if(this.id)a.id=this.id;if(this.className)a.className=this.className;this.el=this.make(this.tagName,a)}}});var n=f.Model.extend=f.Collection.extend=f.View.extend=function(a,b){var c=m(this,a,b);c.extend=n;return c},o={create:"POST",update:"PUT","delete":"DELETE",read:"GET"};f.sync=function(a,b,c,d){var g=
|
15
|
+
a==="create"||a==="update"?{model:JSON.stringify(b)}:{};a=o[a];if(f.emulateHttp&&(a==="PUT"||a==="DELETE")){g._method=a;a="POST"}h.ajax({url:j(b),type:a,data:g,dataType:"json",success:c,error:d})};var m=function(a,b,c){var d;d=b.hasOwnProperty("constructor")?b.constructor:function(){return a.apply(this,arguments)};var g=function(){};g.prototype=a.prototype;d.prototype=new g;e.extend(d.prototype,b);c&&e.extend(d,c);return d.prototype.constructor=d},j=function(a){if(!(a&&a.url))throw Error("A 'url' property or function must be specified");
|
16
|
+
return e.isFunction(a.url)?a.url():a.url}})();
|