ag 0.0.1
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.
- checksums.yaml +7 -0
- data/.gitignore +14 -0
- data/Gemfile +5 -0
- data/LICENSE.txt +22 -0
- data/README.md +64 -0
- data/Rakefile +2 -0
- data/ag.gemspec +24 -0
- data/examples/readme.rb +31 -0
- data/examples/setup.rb +8 -0
- data/lib/ag.rb +11 -0
- data/lib/ag/adapters/memory.rb +62 -0
- data/lib/ag/adapters/sequel_pull.rb +174 -0
- data/lib/ag/adapters/sequel_pull_compact.rb +169 -0
- data/lib/ag/client.rb +31 -0
- data/lib/ag/connection.rb +17 -0
- data/lib/ag/event.rb +17 -0
- data/lib/ag/feed.rb +4 -0
- data/lib/ag/object.rb +17 -0
- data/lib/ag/spec/adapter.rb +179 -0
- data/lib/ag/version.rb +3 -0
- data/script/bootstrap +1 -0
- data/script/test +25 -0
- data/test/adapters/memory_test.rb +41 -0
- data/test/adapters/sequel_pull_compact_test.rb +72 -0
- data/test/adapters/sequel_pull_test.rb +80 -0
- data/test/client_test.rb +107 -0
- data/test/helper.rb +5 -0
- metadata +117 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: d52f18a80b6c4e34a312724af075fb150886ff5d
|
4
|
+
data.tar.gz: 3f27cf30c2fc9fa720f9d9b34102b5beed1715cc
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: b3cd6bdc3b40ed3615ebb1061db3179af0b2168dca2a653644d067b8fd206f237f92c7371c8b8ac1dabf4adccb4ec43ca4a5d46b1b1015e38bbb691d16c1703f
|
7
|
+
data.tar.gz: 28c51284f992d75aaef330826abd918b759ddb3fc128f217fab327496c48b9136ef613b4861ec816fed2f64d0f6127cd99511e0c0d41875c112656693552ff32
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2015 John Nunemaker
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
# Ag
|
2
|
+
|
3
|
+
WORK IN PROGRESS...
|
4
|
+
|
5
|
+
Experiments in describing feeds/timelines of events in code based on adapters so things can work at most levels of throughput.
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
gem "ag"
|
13
|
+
```
|
14
|
+
|
15
|
+
And then execute:
|
16
|
+
|
17
|
+
$ bundle
|
18
|
+
|
19
|
+
Or install it yourself as:
|
20
|
+
|
21
|
+
$ gem install ag
|
22
|
+
|
23
|
+
## Usage
|
24
|
+
|
25
|
+
```ruby
|
26
|
+
require "ag"
|
27
|
+
|
28
|
+
adapter = Ag::Adapters::Memory.new
|
29
|
+
client = Ag::Client.new(adapter)
|
30
|
+
john = Ag::Object.new("User", "1")
|
31
|
+
steve = Ag::Object.new("User", "2")
|
32
|
+
presentation = Ag::Object.new("Presentation", "1")
|
33
|
+
event = Ag::Event.new({
|
34
|
+
producer: steve,
|
35
|
+
object: presentation,
|
36
|
+
verb: "upload_presentation",
|
37
|
+
})
|
38
|
+
|
39
|
+
# connect john to steve
|
40
|
+
pp connect: client.connect(john, steve)
|
41
|
+
|
42
|
+
# is john connected to steve
|
43
|
+
pp connected?: client.connected?(john, steve)
|
44
|
+
|
45
|
+
# consumers of steve
|
46
|
+
pp consumers: client.consumers(steve)
|
47
|
+
|
48
|
+
# producers john is connected to
|
49
|
+
pp producers: client.producers(john)
|
50
|
+
|
51
|
+
# produce an event for steve
|
52
|
+
pp produce: client.produce(event)
|
53
|
+
|
54
|
+
# get the timeline of events for john based on the producers john follows
|
55
|
+
pp timeline: client.timeline(john)
|
56
|
+
```
|
57
|
+
|
58
|
+
## Contributing
|
59
|
+
|
60
|
+
1. Fork it ( https://github.com/jnunemaker/ag/fork )
|
61
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
62
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
63
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
64
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
data/ag.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path("../lib", __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require "ag/version"
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "ag"
|
8
|
+
spec.version = Ag::VERSION
|
9
|
+
spec.authors = ["John Nunemaker"]
|
10
|
+
spec.email = ["nunemaker@gmail.com"]
|
11
|
+
spec.summary = %q{Producers, consumers, connections and timelines.}
|
12
|
+
spec.description = %q{Producers, consumers, connections and timelines.}
|
13
|
+
spec.homepage = ""
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.7"
|
22
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
23
|
+
spec.add_development_dependency "minitest", "~> 5.5.1"
|
24
|
+
end
|
data/examples/readme.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
require_relative "setup"
|
2
|
+
require "ag"
|
3
|
+
|
4
|
+
adapter = Ag::Adapters::Memory.new
|
5
|
+
client = Ag::Client.new(adapter)
|
6
|
+
john = Ag::Object.new("User", "1")
|
7
|
+
steve = Ag::Object.new("User", "2")
|
8
|
+
presentation = Ag::Object.new("Presentation", "1")
|
9
|
+
event = Ag::Event.new({
|
10
|
+
producer: steve,
|
11
|
+
object: presentation,
|
12
|
+
verb: "upload_presentation",
|
13
|
+
})
|
14
|
+
|
15
|
+
# connect john to steve
|
16
|
+
pp connect: client.connect(john, steve)
|
17
|
+
|
18
|
+
# is john connected to steve
|
19
|
+
pp connected?: client.connected?(john, steve)
|
20
|
+
|
21
|
+
# consumers of steve
|
22
|
+
pp consumers: client.consumers(steve)
|
23
|
+
|
24
|
+
# producers john is connected to
|
25
|
+
pp producers: client.producers(john)
|
26
|
+
|
27
|
+
# produce an event for steve
|
28
|
+
pp produce: client.produce(event)
|
29
|
+
|
30
|
+
# get the timeline of events for john based on the producers john follows
|
31
|
+
pp timeline: client.timeline(john)
|
data/examples/setup.rb
ADDED
data/lib/ag.rb
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
require "securerandom"
|
2
|
+
|
3
|
+
module Ag
|
4
|
+
module Adapters
|
5
|
+
class Memory
|
6
|
+
def initialize(source = {})
|
7
|
+
@source = source
|
8
|
+
@source[:connections] ||= []
|
9
|
+
@source[:events] ||= []
|
10
|
+
end
|
11
|
+
|
12
|
+
def connect(consumer, producer)
|
13
|
+
@source[:connections] << Connection.new({
|
14
|
+
id: SecureRandom.uuid,
|
15
|
+
created_at: Time.now.utc,
|
16
|
+
consumer: consumer,
|
17
|
+
producer: producer,
|
18
|
+
})
|
19
|
+
end
|
20
|
+
|
21
|
+
def produce(event)
|
22
|
+
result = Ag::Event.new({
|
23
|
+
id: SecureRandom.uuid,
|
24
|
+
producer: event.producer,
|
25
|
+
object: event.object,
|
26
|
+
verb: event.verb,
|
27
|
+
})
|
28
|
+
@source[:events] << result
|
29
|
+
result
|
30
|
+
end
|
31
|
+
|
32
|
+
def connected?(consumer, producer)
|
33
|
+
!@source[:connections].detect { |connection|
|
34
|
+
connection.consumer == consumer &&
|
35
|
+
connection.producer == producer
|
36
|
+
}.nil?
|
37
|
+
end
|
38
|
+
|
39
|
+
def consumers(producer, options = {})
|
40
|
+
@source[:connections].select { |connection|
|
41
|
+
connection.producer == producer
|
42
|
+
}.reverse[options.fetch(:offset, 0), options.fetch(:limit, 30)]
|
43
|
+
end
|
44
|
+
|
45
|
+
def producers(consumer, options = {})
|
46
|
+
@source[:connections].select { |connection|
|
47
|
+
connection.consumer == consumer
|
48
|
+
}.reverse[options.fetch(:offset, 0), options.fetch(:limit, 30)]
|
49
|
+
end
|
50
|
+
|
51
|
+
def timeline(consumer, options = {})
|
52
|
+
producers = producers(consumer).map(&:producer)
|
53
|
+
|
54
|
+
Array(@source[:events]).select { |event|
|
55
|
+
producers.include?(event.producer)
|
56
|
+
}.sort_by { |event|
|
57
|
+
-event.created_at.to_f
|
58
|
+
}[options.fetch(:offset, 0), options.fetch(:limit, 30)]
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,174 @@
|
|
1
|
+
require "sequel"
|
2
|
+
|
3
|
+
module Ag
|
4
|
+
module Adapters
|
5
|
+
# Adapter that uses the minimum amount of writes while still allowing full
|
6
|
+
# historic assembly of timelines. This comes at the cost of slower reads.
|
7
|
+
class SequelPull
|
8
|
+
def initialize(db)
|
9
|
+
@db = db
|
10
|
+
end
|
11
|
+
|
12
|
+
def connect(consumer, producer)
|
13
|
+
created_at = Time.now.utc
|
14
|
+
id = @db[:connections].insert({
|
15
|
+
consumer_id: consumer.id,
|
16
|
+
consumer_type: consumer.type,
|
17
|
+
producer_id: producer.id,
|
18
|
+
producer_type: producer.type,
|
19
|
+
created_at: created_at,
|
20
|
+
})
|
21
|
+
|
22
|
+
Connection.new({
|
23
|
+
id: id,
|
24
|
+
created_at: created_at,
|
25
|
+
consumer: consumer,
|
26
|
+
producer: producer,
|
27
|
+
})
|
28
|
+
end
|
29
|
+
|
30
|
+
def produce(event)
|
31
|
+
created_at = Time.now.utc
|
32
|
+
id = @db[:events].insert({
|
33
|
+
producer_type: event.producer.type,
|
34
|
+
producer_id: event.producer.id,
|
35
|
+
object_type: event.object.type,
|
36
|
+
object_id: event.object.id,
|
37
|
+
verb: event.verb,
|
38
|
+
created_at: created_at,
|
39
|
+
})
|
40
|
+
|
41
|
+
Ag::Event.new({
|
42
|
+
id: id,
|
43
|
+
created_at: created_at,
|
44
|
+
producer: event.producer,
|
45
|
+
object: event.object,
|
46
|
+
verb: event.verb,
|
47
|
+
})
|
48
|
+
end
|
49
|
+
|
50
|
+
def connected?(consumer, producer)
|
51
|
+
statement = <<-SQL
|
52
|
+
SELECT
|
53
|
+
1
|
54
|
+
FROM
|
55
|
+
connections
|
56
|
+
WHERE
|
57
|
+
consumer_id = :consumer_id AND
|
58
|
+
producer_id = :producer_id
|
59
|
+
LIMIT 1
|
60
|
+
SQL
|
61
|
+
|
62
|
+
binds = {
|
63
|
+
consumer_id: consumer.id,
|
64
|
+
consumer_type: consumer.type,
|
65
|
+
producer_id: producer.id,
|
66
|
+
producer_type: producer.type,
|
67
|
+
}
|
68
|
+
|
69
|
+
!@db[statement, binds].first.nil?
|
70
|
+
end
|
71
|
+
|
72
|
+
def consumers(producer, options = {})
|
73
|
+
statement = <<-SQL
|
74
|
+
SELECT
|
75
|
+
id, created_at, consumer_id, consumer_type, producer_id, producer_type
|
76
|
+
FROM
|
77
|
+
connections
|
78
|
+
WHERE
|
79
|
+
producer_id = :producer_id AND
|
80
|
+
producer_type = :producer_type
|
81
|
+
ORDER BY
|
82
|
+
id DESC
|
83
|
+
LIMIT :limit
|
84
|
+
OFFSET :offset
|
85
|
+
SQL
|
86
|
+
|
87
|
+
binds = {
|
88
|
+
producer_id: producer.id,
|
89
|
+
producer_type: producer.type,
|
90
|
+
limit: options.fetch(:limit, 30),
|
91
|
+
offset: options.fetch(:offset, 0),
|
92
|
+
}
|
93
|
+
|
94
|
+
@db[statement, binds].to_a.map { |row|
|
95
|
+
Connection.new({
|
96
|
+
id: row[:id],
|
97
|
+
created_at: row[:created_at],
|
98
|
+
consumer: Object.new(row[:consumer_type], row[:consumer_id]),
|
99
|
+
producer: Object.new(row[:producer_type], row[:producer_id]),
|
100
|
+
})
|
101
|
+
}
|
102
|
+
end
|
103
|
+
|
104
|
+
def producers(consumer, options = {})
|
105
|
+
statement = <<-SQL
|
106
|
+
SELECT
|
107
|
+
id, created_at, consumer_id, consumer_type, producer_id, producer_type
|
108
|
+
FROM
|
109
|
+
connections
|
110
|
+
WHERE
|
111
|
+
consumer_id = :consumer_id AND
|
112
|
+
consumer_type = :consumer_type
|
113
|
+
ORDER BY
|
114
|
+
id DESC
|
115
|
+
LIMIT :limit
|
116
|
+
OFFSET :offset
|
117
|
+
SQL
|
118
|
+
|
119
|
+
binds = {
|
120
|
+
consumer_id: consumer.id,
|
121
|
+
consumer_type: consumer.type,
|
122
|
+
limit: options.fetch(:limit, 30),
|
123
|
+
offset: options.fetch(:offset, 0),
|
124
|
+
}
|
125
|
+
|
126
|
+
@db[statement, binds].to_a.map { |row|
|
127
|
+
Connection.new({
|
128
|
+
id: row[:id],
|
129
|
+
created_at: row[:created_at],
|
130
|
+
consumer: Object.new(row[:consumer_type], row[:consumer_id]),
|
131
|
+
producer: Object.new(row[:producer_type], row[:producer_id]),
|
132
|
+
})
|
133
|
+
}
|
134
|
+
end
|
135
|
+
|
136
|
+
def timeline(consumer, options = {})
|
137
|
+
statement = <<-SQL
|
138
|
+
SELECT
|
139
|
+
e.*
|
140
|
+
FROM
|
141
|
+
events e
|
142
|
+
INNER JOIN
|
143
|
+
connections c ON
|
144
|
+
e.producer_id = c.producer_id AND
|
145
|
+
e.producer_type = c.producer_type
|
146
|
+
WHERE
|
147
|
+
c.consumer_id = :consumer_id AND
|
148
|
+
c.consumer_type = :consumer_type
|
149
|
+
ORDER BY
|
150
|
+
e.created_at DESC
|
151
|
+
LIMIT :limit
|
152
|
+
OFFSET :offset
|
153
|
+
SQL
|
154
|
+
|
155
|
+
binds = {
|
156
|
+
consumer_id: consumer.id,
|
157
|
+
consumer_type: consumer.type,
|
158
|
+
limit: options.fetch(:limit, 30),
|
159
|
+
offset: options.fetch(:offset, 0),
|
160
|
+
}
|
161
|
+
|
162
|
+
@db[statement, binds].to_a.map { |row|
|
163
|
+
Ag::Event.new({
|
164
|
+
id: row[:id],
|
165
|
+
created_at: row[:created_at],
|
166
|
+
producer: Ag::Object.new(row[:producer_type], row[:producer_id]),
|
167
|
+
object: Ag::Object.new(row[:object_type], row[:object_id]),
|
168
|
+
verb: row[:verb],
|
169
|
+
})
|
170
|
+
}
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
@@ -0,0 +1,169 @@
|
|
1
|
+
require "sequel"
|
2
|
+
|
3
|
+
module Ag
|
4
|
+
module Adapters
|
5
|
+
# Adapter that uses the minimum amount of writes while still allowing full
|
6
|
+
# historic assembly of timelines. This comes at the cost of slower reads.
|
7
|
+
class SequelPullCompact
|
8
|
+
def self.dehydrate(object)
|
9
|
+
[object.type, object.id].join(":")
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.hydrate(id)
|
13
|
+
Ag::Object.new(*id.split(":"))
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize(db)
|
17
|
+
@db = db
|
18
|
+
end
|
19
|
+
|
20
|
+
def connect(consumer, producer)
|
21
|
+
created_at = Time.now.utc
|
22
|
+
id = @db[:connections].insert({
|
23
|
+
consumer_id: self.class.dehydrate(consumer),
|
24
|
+
producer_id: self.class.dehydrate(producer),
|
25
|
+
created_at: created_at,
|
26
|
+
})
|
27
|
+
|
28
|
+
Connection.new({
|
29
|
+
id: id,
|
30
|
+
created_at: created_at,
|
31
|
+
consumer: consumer,
|
32
|
+
producer: producer,
|
33
|
+
})
|
34
|
+
end
|
35
|
+
|
36
|
+
def produce(event)
|
37
|
+
created_at = event.created_at || Time.now.utc
|
38
|
+
id = @db[:events].insert({
|
39
|
+
producer_id: self.class.dehydrate(event.producer),
|
40
|
+
object_id: self.class.dehydrate(event.object),
|
41
|
+
verb: event.verb,
|
42
|
+
created_at: created_at,
|
43
|
+
})
|
44
|
+
|
45
|
+
Ag::Event.new({
|
46
|
+
id: id,
|
47
|
+
created_at: created_at,
|
48
|
+
producer: event.producer,
|
49
|
+
object: event.object,
|
50
|
+
verb: event.verb,
|
51
|
+
})
|
52
|
+
end
|
53
|
+
|
54
|
+
def connected?(consumer, producer)
|
55
|
+
statement = <<-SQL
|
56
|
+
SELECT
|
57
|
+
1
|
58
|
+
FROM
|
59
|
+
connections
|
60
|
+
WHERE
|
61
|
+
consumer_id = :consumer_id AND
|
62
|
+
producer_id = :producer_id
|
63
|
+
LIMIT 1
|
64
|
+
SQL
|
65
|
+
|
66
|
+
binds = {
|
67
|
+
consumer_id: self.class.dehydrate(consumer),
|
68
|
+
producer_id: self.class.dehydrate(producer),
|
69
|
+
}
|
70
|
+
|
71
|
+
!@db[statement, binds].first.nil?
|
72
|
+
end
|
73
|
+
|
74
|
+
def consumers(producer, options = {})
|
75
|
+
statement = <<-SQL
|
76
|
+
SELECT
|
77
|
+
id, created_at, consumer_id, producer_id
|
78
|
+
FROM
|
79
|
+
connections
|
80
|
+
WHERE
|
81
|
+
producer_id = :producer_id
|
82
|
+
ORDER BY
|
83
|
+
id DESC
|
84
|
+
LIMIT :limit
|
85
|
+
OFFSET :offset
|
86
|
+
SQL
|
87
|
+
|
88
|
+
binds = {
|
89
|
+
producer_id: self.class.dehydrate(producer),
|
90
|
+
limit: options.fetch(:limit, 30),
|
91
|
+
offset: options.fetch(:offset, 0),
|
92
|
+
}
|
93
|
+
|
94
|
+
@db[statement, binds].to_a.map { |row|
|
95
|
+
Connection.new({
|
96
|
+
id: row[:id],
|
97
|
+
created_at: row[:created_at],
|
98
|
+
consumer: self.class.hydrate(row[:consumer_id]),
|
99
|
+
producer: self.class.hydrate(row[:producer_id]),
|
100
|
+
})
|
101
|
+
}
|
102
|
+
end
|
103
|
+
|
104
|
+
def producers(consumer, options = {})
|
105
|
+
statement = <<-SQL
|
106
|
+
SELECT
|
107
|
+
id, created_at, consumer_id, producer_id
|
108
|
+
FROM
|
109
|
+
connections
|
110
|
+
WHERE
|
111
|
+
consumer_id = :consumer_id
|
112
|
+
ORDER BY
|
113
|
+
id DESC
|
114
|
+
LIMIT :limit
|
115
|
+
OFFSET :offset
|
116
|
+
SQL
|
117
|
+
|
118
|
+
binds = {
|
119
|
+
consumer_id: self.class.dehydrate(consumer),
|
120
|
+
limit: options.fetch(:limit, 30),
|
121
|
+
offset: options.fetch(:offset, 0),
|
122
|
+
}
|
123
|
+
|
124
|
+
@db[statement, binds].to_a.map { |row|
|
125
|
+
Connection.new({
|
126
|
+
id: row[:id],
|
127
|
+
created_at: row[:created_at],
|
128
|
+
consumer: self.class.hydrate(row[:consumer_id]),
|
129
|
+
producer: self.class.hydrate(row[:producer_id]),
|
130
|
+
})
|
131
|
+
}
|
132
|
+
end
|
133
|
+
|
134
|
+
def timeline(consumer, options = {})
|
135
|
+
statement = <<-SQL
|
136
|
+
SELECT
|
137
|
+
e.*
|
138
|
+
FROM
|
139
|
+
events e
|
140
|
+
INNER JOIN
|
141
|
+
connections c ON
|
142
|
+
e.producer_id = c.producer_id
|
143
|
+
WHERE
|
144
|
+
c.consumer_id = :consumer_id
|
145
|
+
ORDER BY
|
146
|
+
e.created_at DESC
|
147
|
+
LIMIT :limit
|
148
|
+
OFFSET :offset
|
149
|
+
SQL
|
150
|
+
|
151
|
+
binds = {
|
152
|
+
consumer_id: self.class.dehydrate(consumer),
|
153
|
+
limit: options.fetch(:limit, 30),
|
154
|
+
offset: options.fetch(:offset, 0),
|
155
|
+
}
|
156
|
+
|
157
|
+
@db[statement, binds].to_a.map { |row|
|
158
|
+
Ag::Event.new({
|
159
|
+
id: row[:id],
|
160
|
+
created_at: row[:created_at],
|
161
|
+
producer: self.class.hydrate(row[:producer_id]),
|
162
|
+
object: self.class.hydrate(row[:object_id]),
|
163
|
+
verb: row[:verb],
|
164
|
+
})
|
165
|
+
}
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
data/lib/ag/client.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
module Ag
|
2
|
+
class Client
|
3
|
+
def initialize(adapter)
|
4
|
+
@adapter = adapter
|
5
|
+
end
|
6
|
+
|
7
|
+
def connect(consumer, producer)
|
8
|
+
@adapter.connect(consumer, producer)
|
9
|
+
end
|
10
|
+
|
11
|
+
def produce(event)
|
12
|
+
@adapter.produce(event)
|
13
|
+
end
|
14
|
+
|
15
|
+
def connected?(consumer, producer)
|
16
|
+
@adapter.connected?(consumer, producer)
|
17
|
+
end
|
18
|
+
|
19
|
+
def consumers(producer, options = {})
|
20
|
+
@adapter.consumers(producer, options)
|
21
|
+
end
|
22
|
+
|
23
|
+
def producers(consumer, options = {})
|
24
|
+
@adapter.producers(consumer, options)
|
25
|
+
end
|
26
|
+
|
27
|
+
def timeline(consumer, options = {})
|
28
|
+
@adapter.timeline(consumer, options)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require "forwardable"
|
2
|
+
|
3
|
+
module Ag
|
4
|
+
class Connection
|
5
|
+
attr_reader :id
|
6
|
+
attr_reader :producer
|
7
|
+
attr_reader :consumer
|
8
|
+
attr_reader :created_at
|
9
|
+
|
10
|
+
def initialize(attributes = {})
|
11
|
+
@id = attributes[:id]
|
12
|
+
@producer = attributes[:producer]
|
13
|
+
@consumer = attributes[:consumer]
|
14
|
+
@created_at = attributes[:created_at]
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/lib/ag/event.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
module Ag
|
2
|
+
class Event
|
3
|
+
attr_reader :id
|
4
|
+
attr_reader :producer
|
5
|
+
attr_reader :object
|
6
|
+
attr_reader :verb
|
7
|
+
attr_reader :created_at
|
8
|
+
|
9
|
+
def initialize(attrs = {})
|
10
|
+
@id = attrs[:id]
|
11
|
+
@producer = attrs.fetch(:producer)
|
12
|
+
@object = attrs.fetch(:object)
|
13
|
+
@verb = attrs.fetch(:verb)
|
14
|
+
@created_at = attrs.fetch(:created_at) { Time.now.utc }
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/lib/ag/feed.rb
ADDED
data/lib/ag/object.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
module Ag
|
2
|
+
class Object
|
3
|
+
attr_reader :type
|
4
|
+
attr_reader :id
|
5
|
+
|
6
|
+
def initialize(type, id)
|
7
|
+
@type = type
|
8
|
+
@id = id
|
9
|
+
end
|
10
|
+
|
11
|
+
def ==(other)
|
12
|
+
self.class == other.class &&
|
13
|
+
self.type == other.type &&
|
14
|
+
self.id == other.id
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,179 @@
|
|
1
|
+
module Ag
|
2
|
+
module Spec
|
3
|
+
module Adapter
|
4
|
+
def test_connect
|
5
|
+
consumer = Ag::Object.new("User", "1")
|
6
|
+
producer = Ag::Object.new("User", "2")
|
7
|
+
|
8
|
+
adapter.connect(consumer, producer)
|
9
|
+
|
10
|
+
connection = connections.first
|
11
|
+
assert_equal consumer.id, connection.consumer.id
|
12
|
+
assert_equal consumer.type, connection.consumer.type
|
13
|
+
assert_equal producer.id, connection.producer.id
|
14
|
+
assert_equal producer.type, connection.producer.type
|
15
|
+
assert_in_delta Time.now.utc, connection.created_at, 1
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_produce
|
19
|
+
producer = Ag::Object.new("User", "1")
|
20
|
+
object = Ag::Object.new("User", "2")
|
21
|
+
event = Ag::Event.new({
|
22
|
+
producer: producer,
|
23
|
+
object: object,
|
24
|
+
verb: "follow",
|
25
|
+
})
|
26
|
+
|
27
|
+
result = adapter.produce(event)
|
28
|
+
|
29
|
+
event = events.first
|
30
|
+
assert_equal event.id, result.id
|
31
|
+
assert_equal producer.id, event.producer.id
|
32
|
+
assert_equal producer.type, event.producer.type
|
33
|
+
assert_equal object.id, event.object.id
|
34
|
+
assert_equal object.type, event.object.type
|
35
|
+
assert_in_delta Time.now.utc, event.created_at, 1
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_connected
|
39
|
+
consumer = Ag::Object.new("User", "1")
|
40
|
+
producer = Ag::Object.new("User", "2")
|
41
|
+
connect(consumer, producer)
|
42
|
+
|
43
|
+
assert_equal true, adapter.connected?(consumer, producer)
|
44
|
+
assert_equal false, adapter.connected?(producer, consumer)
|
45
|
+
end
|
46
|
+
|
47
|
+
def test_consumers
|
48
|
+
consumer1 = Ag::Object.new("User", "1")
|
49
|
+
consumer2 = Ag::Object.new("User", "2")
|
50
|
+
consumer3 = Ag::Object.new("User", "3")
|
51
|
+
producer = Ag::Object.new("User", "4")
|
52
|
+
connect(consumer1, producer)
|
53
|
+
connect(consumer2, producer)
|
54
|
+
|
55
|
+
consumers = adapter.consumers(producer)
|
56
|
+
assert_equal 2, consumers.size
|
57
|
+
assert_equal "2", consumers[0].consumer.id
|
58
|
+
assert_equal "1", consumers[1].consumer.id
|
59
|
+
end
|
60
|
+
|
61
|
+
def test_consumers_limit
|
62
|
+
producer = Ag::Object.new("User", "99")
|
63
|
+
consumers = (1..10).to_a.map { |n|
|
64
|
+
Ag::Object.new("User", n.to_s).tap { |consumer|
|
65
|
+
connect consumer, producer
|
66
|
+
}
|
67
|
+
}
|
68
|
+
assert_equal 5, adapter.consumers(producer, limit: 5).size
|
69
|
+
assert_equal consumers[5..9].reverse,
|
70
|
+
adapter.consumers(producer, limit: 5).map(&:consumer)
|
71
|
+
end
|
72
|
+
|
73
|
+
def test_consumers_offset
|
74
|
+
producer = Ag::Object.new("User", "99")
|
75
|
+
consumers = (1..10).to_a.map { |n|
|
76
|
+
Ag::Object.new("User", n.to_s).tap { |consumer|
|
77
|
+
connect consumer, producer
|
78
|
+
}
|
79
|
+
}
|
80
|
+
assert_equal consumers[0..4].reverse,
|
81
|
+
adapter.consumers(producer, offset: 5).map(&:consumer)
|
82
|
+
end
|
83
|
+
|
84
|
+
def test_producers
|
85
|
+
consumer1 = Ag::Object.new("User", "1")
|
86
|
+
consumer2 = Ag::Object.new("User", "2")
|
87
|
+
producer1 = Ag::Object.new("User", "3")
|
88
|
+
producer2 = Ag::Object.new("User", "4")
|
89
|
+
producer3 = Ag::Object.new("User", "5")
|
90
|
+
connect(consumer1, producer1)
|
91
|
+
connect(consumer1, producer2)
|
92
|
+
connect(consumer2, producer3)
|
93
|
+
|
94
|
+
producers = adapter.producers(consumer1)
|
95
|
+
assert_equal 2, producers.size
|
96
|
+
assert_equal "4", producers[0].producer.id
|
97
|
+
assert_equal "3", producers[1].producer.id
|
98
|
+
end
|
99
|
+
|
100
|
+
def test_producers_limit
|
101
|
+
consumer = Ag::Object.new("User", "99")
|
102
|
+
producers = (1..10).to_a.map { |n|
|
103
|
+
Ag::Object.new("User", n.to_s).tap { |producer|
|
104
|
+
connect consumer, producer
|
105
|
+
}
|
106
|
+
}
|
107
|
+
assert_equal 5, adapter.producers(consumer, limit: 5).size
|
108
|
+
assert_equal producers[5..9].reverse,
|
109
|
+
adapter.producers(consumer, limit: 5).map(&:producer)
|
110
|
+
end
|
111
|
+
|
112
|
+
def test_producers_offset
|
113
|
+
consumer = Ag::Object.new("User", "99")
|
114
|
+
producers = (1..10).to_a.map { |n|
|
115
|
+
Ag::Object.new("User", n.to_s).tap { |producer|
|
116
|
+
connect consumer, producer
|
117
|
+
}
|
118
|
+
}
|
119
|
+
assert_equal producers[0..4].reverse,
|
120
|
+
adapter.producers(consumer, offset: 5).map(&:producer)
|
121
|
+
end
|
122
|
+
|
123
|
+
def test_timeline
|
124
|
+
john = Ag::Object.new("User", "1")
|
125
|
+
steve = Ag::Object.new("User", "2")
|
126
|
+
presentation = Ag::Object.new("Presentation", "1")
|
127
|
+
connect john, steve
|
128
|
+
produce Ag::Event.new(producer: steve, object: presentation, verb: "publish")
|
129
|
+
|
130
|
+
events = adapter.timeline(john)
|
131
|
+
assert_equal 1, events.size
|
132
|
+
end
|
133
|
+
|
134
|
+
def test_timeline_limit
|
135
|
+
john = Ag::Object.new("User", "1")
|
136
|
+
steve = Ag::Object.new("User", "2")
|
137
|
+
connect john, steve
|
138
|
+
|
139
|
+
presentations = (1..10).to_a.map { |n|
|
140
|
+
Ag::Object.new("Presentation", n.to_s)
|
141
|
+
}
|
142
|
+
|
143
|
+
presentations.each do |presentation|
|
144
|
+
produce Ag::Event.new({
|
145
|
+
producer: steve,
|
146
|
+
object: presentation,
|
147
|
+
verb: "publish",
|
148
|
+
})
|
149
|
+
end
|
150
|
+
|
151
|
+
events = adapter.timeline(john, limit: 5)
|
152
|
+
assert_equal 5, events.size
|
153
|
+
assert_equal presentations[5..9].reverse, events.map(&:object)
|
154
|
+
end
|
155
|
+
|
156
|
+
def test_timeline_offset
|
157
|
+
john = Ag::Object.new("User", "1")
|
158
|
+
steve = Ag::Object.new("User", "2")
|
159
|
+
connect john, steve
|
160
|
+
|
161
|
+
presentations = (1..10).to_a.map { |n|
|
162
|
+
Ag::Object.new("Presentation", n.to_s)
|
163
|
+
}
|
164
|
+
|
165
|
+
presentations.each do |presentation|
|
166
|
+
produce Ag::Event.new({
|
167
|
+
producer: steve,
|
168
|
+
object: presentation,
|
169
|
+
verb: "publish",
|
170
|
+
})
|
171
|
+
end
|
172
|
+
|
173
|
+
events = adapter.timeline(john, offset: 5)
|
174
|
+
assert_equal 5, events.size
|
175
|
+
assert_equal presentations[0..4].reverse, events.map(&:object)
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
data/lib/ag/version.rb
ADDED
data/script/bootstrap
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
bundle --quiet
|
data/script/test
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
#!/bin/sh
|
2
|
+
#/ Usage: test [individual test file]
|
3
|
+
#/
|
4
|
+
#/ Bootstrap and run all tests or an individual test.
|
5
|
+
#/
|
6
|
+
#/ Examples:
|
7
|
+
#/
|
8
|
+
#/ # run all tests
|
9
|
+
#/ test
|
10
|
+
#/
|
11
|
+
#/ # run individual test
|
12
|
+
#/ test test/controller_instrumentation_test.rb
|
13
|
+
#/
|
14
|
+
|
15
|
+
set -e
|
16
|
+
cd $(dirname "$0")/..
|
17
|
+
|
18
|
+
[ "$1" = "--help" -o "$1" = "-h" -o "$1" = "help" ] && {
|
19
|
+
grep '^#/' <"$0"| cut -c4-
|
20
|
+
exit 0
|
21
|
+
}
|
22
|
+
|
23
|
+
ruby -I lib -I test -r rubygems \
|
24
|
+
-e 'require "bundler/setup"' \
|
25
|
+
-e '(ARGV.empty? ? Dir["test/**/*_test.rb"] : ARGV).each { |f| load f }' -- "$@"
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require_relative "../helper"
|
2
|
+
require "ag/adapters/memory"
|
3
|
+
require "ag/spec/adapter"
|
4
|
+
require "securerandom"
|
5
|
+
|
6
|
+
class AdaptersMemoryTest < Ag::Test
|
7
|
+
def setup
|
8
|
+
@source = {}
|
9
|
+
end
|
10
|
+
|
11
|
+
def adapter
|
12
|
+
@adapter ||= Ag::Adapters::Memory.new(@source)
|
13
|
+
end
|
14
|
+
|
15
|
+
include Ag::Spec::Adapter
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def connections
|
20
|
+
@source[:connections]
|
21
|
+
end
|
22
|
+
|
23
|
+
def events
|
24
|
+
@source[:events]
|
25
|
+
end
|
26
|
+
|
27
|
+
def connect(consumer, producer)
|
28
|
+
@source[:connections] ||= []
|
29
|
+
@source[:connections] << Ag::Connection.new({
|
30
|
+
id: SecureRandom.uuid,
|
31
|
+
created_at: Time.now.utc,
|
32
|
+
consumer: consumer,
|
33
|
+
producer: producer,
|
34
|
+
})
|
35
|
+
end
|
36
|
+
|
37
|
+
def produce(event)
|
38
|
+
@source[:events] ||= []
|
39
|
+
@source[:events] << event
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require_relative "../helper"
|
2
|
+
require "ag/adapters/sequel_pull_compact"
|
3
|
+
require "ag/spec/adapter"
|
4
|
+
|
5
|
+
class AdaptersSequelPullCompactTest < Ag::Test
|
6
|
+
def setup
|
7
|
+
Sequel.default_timezone = :utc
|
8
|
+
@db = Sequel.sqlite
|
9
|
+
|
10
|
+
@db.create_table :connections do
|
11
|
+
primary_key :id
|
12
|
+
String :consumer_id
|
13
|
+
String :producer_id
|
14
|
+
Time :created_at
|
15
|
+
index [:consumer_id, :producer_id], unique: true
|
16
|
+
end
|
17
|
+
|
18
|
+
@db.create_table :events do
|
19
|
+
primary_key :id
|
20
|
+
String :producer_id
|
21
|
+
String :object_id
|
22
|
+
String :verb
|
23
|
+
Time :created_at
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def adapter
|
28
|
+
@adapter ||= Ag::Adapters::SequelPullCompact.new(@db)
|
29
|
+
end
|
30
|
+
|
31
|
+
include Ag::Spec::Adapter
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def connections
|
36
|
+
@db[:connections].map { |row|
|
37
|
+
Ag::Connection.new({
|
38
|
+
id: row[:id],
|
39
|
+
created_at: row[:created_at],
|
40
|
+
consumer: Ag::Adapters::SequelPullCompact.hydrate(row[:consumer_id]),
|
41
|
+
producer: Ag::Adapters::SequelPullCompact.hydrate(row[:producer_id]),
|
42
|
+
})
|
43
|
+
}
|
44
|
+
end
|
45
|
+
|
46
|
+
def events
|
47
|
+
@db[:events].map { |row|
|
48
|
+
Ag::Event.new({
|
49
|
+
id: row[:id],
|
50
|
+
verb: row[:verb],
|
51
|
+
created_at: row[:created_at],
|
52
|
+
producer: Ag::Adapters::SequelPullCompact.hydrate(row[:producer_id]),
|
53
|
+
object: Ag::Adapters::SequelPullCompact.hydrate(row[:object_id]),
|
54
|
+
})
|
55
|
+
}
|
56
|
+
end
|
57
|
+
|
58
|
+
def connect(consumer, producer)
|
59
|
+
@db[:connections].insert({
|
60
|
+
consumer_id: Ag::Adapters::SequelPullCompact.dehydrate(consumer),
|
61
|
+
producer_id: Ag::Adapters::SequelPullCompact.dehydrate(producer),
|
62
|
+
})
|
63
|
+
end
|
64
|
+
|
65
|
+
def produce(event)
|
66
|
+
@db[:events].insert({
|
67
|
+
producer_id: Ag::Adapters::SequelPullCompact.dehydrate(event.producer),
|
68
|
+
object_id: Ag::Adapters::SequelPullCompact.dehydrate(event.object),
|
69
|
+
created_at: Time.now.utc,
|
70
|
+
})
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
require_relative "../helper"
|
2
|
+
require "ag/adapters/sequel_pull"
|
3
|
+
require "ag/spec/adapter"
|
4
|
+
|
5
|
+
class AdaptersSequelPullTest < Ag::Test
|
6
|
+
def setup
|
7
|
+
Sequel.default_timezone = :utc
|
8
|
+
@db = Sequel.sqlite
|
9
|
+
|
10
|
+
@db.create_table :connections do
|
11
|
+
primary_key :id
|
12
|
+
String :consumer_id
|
13
|
+
String :consumer_type
|
14
|
+
String :producer_id
|
15
|
+
String :producer_type
|
16
|
+
Time :created_at
|
17
|
+
index [:consumer_id, :consumer_type, :producer_id, :producer_type], unique: true
|
18
|
+
end
|
19
|
+
|
20
|
+
@db.create_table :events do
|
21
|
+
primary_key :id
|
22
|
+
String :producer_id
|
23
|
+
String :producer_type
|
24
|
+
String :object_id
|
25
|
+
String :object_type
|
26
|
+
String :verb
|
27
|
+
Time :created_at
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def adapter
|
32
|
+
@adapter ||= Ag::Adapters::SequelPull.new(@db)
|
33
|
+
end
|
34
|
+
|
35
|
+
include Ag::Spec::Adapter
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def connections
|
40
|
+
@db[:connections].map { |row|
|
41
|
+
Ag::Connection.new({
|
42
|
+
id: row[:id],
|
43
|
+
created_at: row[:created_at],
|
44
|
+
consumer: Ag::Object.new(row[:consumer_type], row[:consumer_id]),
|
45
|
+
producer: Ag::Object.new(row[:producer_type], row[:producer_id]),
|
46
|
+
})
|
47
|
+
}
|
48
|
+
end
|
49
|
+
|
50
|
+
def events
|
51
|
+
@db[:events].map { |row|
|
52
|
+
Ag::Event.new({
|
53
|
+
id: row[:id],
|
54
|
+
verb: row[:verb],
|
55
|
+
created_at: row[:created_at],
|
56
|
+
producer: Ag::Object.new(row[:producer_type], row[:producer_id]),
|
57
|
+
object: Ag::Object.new(row[:object_type], row[:object_id]),
|
58
|
+
})
|
59
|
+
}
|
60
|
+
end
|
61
|
+
|
62
|
+
def connect(consumer, producer)
|
63
|
+
@db[:connections].insert({
|
64
|
+
consumer_id: consumer.id,
|
65
|
+
consumer_type: consumer.type,
|
66
|
+
producer_id: producer.id,
|
67
|
+
producer_type: producer.type,
|
68
|
+
})
|
69
|
+
end
|
70
|
+
|
71
|
+
def produce(event)
|
72
|
+
@db[:events].insert({
|
73
|
+
producer_type: event.producer.type,
|
74
|
+
producer_id: event.producer.id,
|
75
|
+
object_type: event.object.type,
|
76
|
+
object_id: event.object.id,
|
77
|
+
created_at: Time.now.utc,
|
78
|
+
})
|
79
|
+
end
|
80
|
+
end
|
data/test/client_test.rb
ADDED
@@ -0,0 +1,107 @@
|
|
1
|
+
require "helper"
|
2
|
+
|
3
|
+
class ClientTest < Ag::Test
|
4
|
+
def test_initializes_with_adapter
|
5
|
+
adapter = Ag::Adapters::Memory.new
|
6
|
+
client = Ag::Client.new(adapter)
|
7
|
+
assert_instance_of Ag::Client, client
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_forwards_connect_to_adapter
|
11
|
+
args = [consumer, producer]
|
12
|
+
result = true
|
13
|
+
mock_adapter = Minitest::Mock.new
|
14
|
+
mock_adapter.expect(:connect, result, args)
|
15
|
+
|
16
|
+
client = Ag::Client.new(mock_adapter)
|
17
|
+
assert_equal result, client.connect(*args)
|
18
|
+
|
19
|
+
mock_adapter.verify
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_forwards_produce_to_adapter
|
23
|
+
args = [event]
|
24
|
+
result = [producer]
|
25
|
+
mock_adapter = Minitest::Mock.new
|
26
|
+
mock_adapter.expect(:produce, result, args)
|
27
|
+
|
28
|
+
client = Ag::Client.new(mock_adapter)
|
29
|
+
assert_equal result, client.produce(*args)
|
30
|
+
|
31
|
+
mock_adapter.verify
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_forwards_connected_to_adapter
|
35
|
+
args = [consumer, producer]
|
36
|
+
result = true
|
37
|
+
mock_adapter = Minitest::Mock.new
|
38
|
+
mock_adapter.expect(:connected?, result, args)
|
39
|
+
|
40
|
+
client = Ag::Client.new(mock_adapter)
|
41
|
+
assert_equal result, client.connected?(*args)
|
42
|
+
|
43
|
+
mock_adapter.verify
|
44
|
+
end
|
45
|
+
|
46
|
+
def test_forwards_consumers_to_adapter
|
47
|
+
args = [producer, {}]
|
48
|
+
result = [consumer]
|
49
|
+
mock_adapter = Minitest::Mock.new
|
50
|
+
mock_adapter.expect(:consumers, result, args)
|
51
|
+
|
52
|
+
client = Ag::Client.new(mock_adapter)
|
53
|
+
assert_equal result, client.consumers(*args)
|
54
|
+
|
55
|
+
mock_adapter.verify
|
56
|
+
end
|
57
|
+
|
58
|
+
def test_forwards_producers_to_adapter
|
59
|
+
args = [consumer, {}]
|
60
|
+
result = [producer]
|
61
|
+
mock_adapter = Minitest::Mock.new
|
62
|
+
mock_adapter.expect(:producers, result, args)
|
63
|
+
|
64
|
+
client = Ag::Client.new(mock_adapter)
|
65
|
+
assert_equal result, client.producers(*args)
|
66
|
+
|
67
|
+
mock_adapter.verify
|
68
|
+
end
|
69
|
+
|
70
|
+
def test_forwards_timeline_to_adapter
|
71
|
+
args = [consumer, {}]
|
72
|
+
result = [event]
|
73
|
+
mock_adapter = Minitest::Mock.new
|
74
|
+
mock_adapter.expect(:timeline, result, args)
|
75
|
+
|
76
|
+
client = Ag::Client.new(mock_adapter)
|
77
|
+
assert_equal result, client.timeline(*args)
|
78
|
+
|
79
|
+
mock_adapter.verify
|
80
|
+
end
|
81
|
+
|
82
|
+
private
|
83
|
+
|
84
|
+
def event
|
85
|
+
@event ||= Ag::Event.new({
|
86
|
+
producer: producer,
|
87
|
+
object: object,
|
88
|
+
verb: verb,
|
89
|
+
})
|
90
|
+
end
|
91
|
+
|
92
|
+
def verb
|
93
|
+
"follow"
|
94
|
+
end
|
95
|
+
|
96
|
+
def consumer
|
97
|
+
@consumer ||= Ag::Object.new("User", "1")
|
98
|
+
end
|
99
|
+
|
100
|
+
def producer
|
101
|
+
@producer ||= Ag::Object.new("User", "1")
|
102
|
+
end
|
103
|
+
|
104
|
+
def object
|
105
|
+
@object ||= Ag::Object.new("User", "3")
|
106
|
+
end
|
107
|
+
end
|
data/test/helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,117 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ag
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- John Nunemaker
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-02-15 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.7'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.7'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: minitest
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 5.5.1
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 5.5.1
|
55
|
+
description: Producers, consumers, connections and timelines.
|
56
|
+
email:
|
57
|
+
- nunemaker@gmail.com
|
58
|
+
executables: []
|
59
|
+
extensions: []
|
60
|
+
extra_rdoc_files: []
|
61
|
+
files:
|
62
|
+
- ".gitignore"
|
63
|
+
- Gemfile
|
64
|
+
- LICENSE.txt
|
65
|
+
- README.md
|
66
|
+
- Rakefile
|
67
|
+
- ag.gemspec
|
68
|
+
- examples/readme.rb
|
69
|
+
- examples/setup.rb
|
70
|
+
- lib/ag.rb
|
71
|
+
- lib/ag/adapters/memory.rb
|
72
|
+
- lib/ag/adapters/sequel_pull.rb
|
73
|
+
- lib/ag/adapters/sequel_pull_compact.rb
|
74
|
+
- lib/ag/client.rb
|
75
|
+
- lib/ag/connection.rb
|
76
|
+
- lib/ag/event.rb
|
77
|
+
- lib/ag/feed.rb
|
78
|
+
- lib/ag/object.rb
|
79
|
+
- lib/ag/spec/adapter.rb
|
80
|
+
- lib/ag/version.rb
|
81
|
+
- script/bootstrap
|
82
|
+
- script/test
|
83
|
+
- test/adapters/memory_test.rb
|
84
|
+
- test/adapters/sequel_pull_compact_test.rb
|
85
|
+
- test/adapters/sequel_pull_test.rb
|
86
|
+
- test/client_test.rb
|
87
|
+
- test/helper.rb
|
88
|
+
homepage: ''
|
89
|
+
licenses:
|
90
|
+
- MIT
|
91
|
+
metadata: {}
|
92
|
+
post_install_message:
|
93
|
+
rdoc_options: []
|
94
|
+
require_paths:
|
95
|
+
- lib
|
96
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
97
|
+
requirements:
|
98
|
+
- - ">="
|
99
|
+
- !ruby/object:Gem::Version
|
100
|
+
version: '0'
|
101
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
102
|
+
requirements:
|
103
|
+
- - ">="
|
104
|
+
- !ruby/object:Gem::Version
|
105
|
+
version: '0'
|
106
|
+
requirements: []
|
107
|
+
rubyforge_project:
|
108
|
+
rubygems_version: 2.2.2
|
109
|
+
signing_key:
|
110
|
+
specification_version: 4
|
111
|
+
summary: Producers, consumers, connections and timelines.
|
112
|
+
test_files:
|
113
|
+
- test/adapters/memory_test.rb
|
114
|
+
- test/adapters/sequel_pull_compact_test.rb
|
115
|
+
- test/adapters/sequel_pull_test.rb
|
116
|
+
- test/client_test.rb
|
117
|
+
- test/helper.rb
|