dam 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +20 -0
- data/README.markdown +2 -0
- data/Rakefile +34 -0
- data/lib/dam/activity.rb +120 -0
- data/lib/dam/storage.rb +40 -0
- data/lib/dam/stream.rb +211 -0
- data/lib/dam/version.rb +3 -0
- data/lib/dam.rb +39 -0
- data/test/activity_test.rb +62 -0
- data/test/stream_test.rb +106 -0
- data/test/test_helper.rb +19 -0
- metadata +107 -0
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Jean-Philippe Bougie
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.markdown
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
$LOAD_PATH.unshift File.dirname(__FILE__) + '/lib'
|
2
|
+
|
3
|
+
task :default => :test
|
4
|
+
|
5
|
+
require 'rake/testtask'
|
6
|
+
Rake::TestTask.new(:test) do |test|
|
7
|
+
test.libs << 'lib' << 'test'
|
8
|
+
test.pattern = 'test/**/*_test.rb'
|
9
|
+
test.verbose = false
|
10
|
+
end
|
11
|
+
|
12
|
+
|
13
|
+
begin
|
14
|
+
require 'jeweler'
|
15
|
+
require 'dam/version'
|
16
|
+
|
17
|
+
Jeweler::Tasks.new do |gemspec|
|
18
|
+
gemspec.name = "dam"
|
19
|
+
gemspec.summary = "An activity stream framework for Ruby"
|
20
|
+
gemspec.description = ""
|
21
|
+
gemspec.email = "jp.bougie@gmail.com"
|
22
|
+
gemspec.homepage = "http://github.com/jpbougie/dam"
|
23
|
+
gemspec.authors = ["Jean-Philippe Bougie"]
|
24
|
+
gemspec.version = Dam::Version
|
25
|
+
|
26
|
+
gemspec.add_dependency "redis"
|
27
|
+
gemspec.add_dependency "yajl-ruby"
|
28
|
+
gemspec.add_development_dependency "jeweler"
|
29
|
+
gemspec.add_development_dependency "riot"
|
30
|
+
end
|
31
|
+
rescue LoadError
|
32
|
+
puts "Jeweler not available. Install it with: "
|
33
|
+
puts "gem install jeweler"
|
34
|
+
end
|
data/lib/dam/activity.rb
ADDED
@@ -0,0 +1,120 @@
|
|
1
|
+
module Dam
|
2
|
+
|
3
|
+
private
|
4
|
+
|
5
|
+
class TypeProxy
|
6
|
+
instance_methods.each { |m| undef_method m unless m =~ /(^__|instance_eval)/ }
|
7
|
+
|
8
|
+
def initialize(type)
|
9
|
+
@type = type
|
10
|
+
end
|
11
|
+
|
12
|
+
def method_missing(meth, arg=nil, &block)
|
13
|
+
|
14
|
+
# the attribute can either be a static value, or a block to be evaluated, not both
|
15
|
+
raise ArgumentError unless (!arg.nil? ^ block_given?)
|
16
|
+
|
17
|
+
if block_given?
|
18
|
+
@type.add_attribute(:name => meth, :block => block)
|
19
|
+
else
|
20
|
+
@type.add_attribute(:name => meth, :value => arg)
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class Context
|
27
|
+
attr_reader :params
|
28
|
+
def initialize(params); @params = params; end
|
29
|
+
end
|
30
|
+
|
31
|
+
public
|
32
|
+
|
33
|
+
class ActivityType
|
34
|
+
attr_accessor :attributes
|
35
|
+
attr_reader :name
|
36
|
+
|
37
|
+
# Class methods
|
38
|
+
def self.register(type, act)
|
39
|
+
@activity_types ||= {}
|
40
|
+
@activity_types[type] = act
|
41
|
+
|
42
|
+
act
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.lookup(type)
|
46
|
+
@activity_types ||= {}
|
47
|
+
@activity_types[type]
|
48
|
+
end
|
49
|
+
|
50
|
+
# Instance methods
|
51
|
+
def initialize(name, &block)
|
52
|
+
@attributes = {}
|
53
|
+
@name = name
|
54
|
+
|
55
|
+
proxy = TypeProxy.new(self)
|
56
|
+
|
57
|
+
proxy.instance_eval(&block)
|
58
|
+
end
|
59
|
+
|
60
|
+
def add_attribute(params = {})
|
61
|
+
@attributes[params[:name].to_s] = params[:value] || params[:block]
|
62
|
+
self
|
63
|
+
end
|
64
|
+
|
65
|
+
def apply(params = {})
|
66
|
+
context = Context.new(params)
|
67
|
+
evaluated_attributes = {}
|
68
|
+
@attributes.each_pair do |attribute, value|
|
69
|
+
evaluated_attributes[attribute.to_s] = if value.respond_to? :call
|
70
|
+
context.instance_eval(&value)
|
71
|
+
else
|
72
|
+
value
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
Activity.new(self.name, evaluated_attributes)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
class Activity
|
81
|
+
|
82
|
+
def self.[](name)
|
83
|
+
Dam::ActivityType.lookup(name)
|
84
|
+
end
|
85
|
+
|
86
|
+
attr_accessor :attributes
|
87
|
+
def initialize(type, params = {})
|
88
|
+
@attributes = params
|
89
|
+
@type = type
|
90
|
+
end
|
91
|
+
|
92
|
+
def ==(other)
|
93
|
+
@type == other.instance_variable_get("@type") && attributes == other.attributes
|
94
|
+
end
|
95
|
+
|
96
|
+
def post!
|
97
|
+
Dam.push(self)
|
98
|
+
end
|
99
|
+
|
100
|
+
def self.from_json json
|
101
|
+
attributes = Yajl::Parser.parse(json)
|
102
|
+
type = attributes.delete("_type").to_sym
|
103
|
+
new(type, attributes)
|
104
|
+
end
|
105
|
+
|
106
|
+
def to_json
|
107
|
+
Yajl::Encoder.encode(self.attributes.merge({:_type => @type}))
|
108
|
+
end
|
109
|
+
|
110
|
+
private
|
111
|
+
|
112
|
+
def method_missing(meth, *args, &block)
|
113
|
+
if @attributes.has_key? meth.to_s
|
114
|
+
@attributes[meth.to_s]
|
115
|
+
else
|
116
|
+
super
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
data/lib/dam/storage.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'redis'
|
2
|
+
|
3
|
+
module Dam
|
4
|
+
class Storage
|
5
|
+
def Storage.register(name, engine)
|
6
|
+
@engines ||= {}
|
7
|
+
@engines[name] = engine
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.database
|
11
|
+
return @database if instance_variable_defined? '@database'
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.database=(database)
|
15
|
+
@database = database
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.insert(stream, activity)
|
19
|
+
key = stream.name
|
20
|
+
|
21
|
+
self.database.push_head("stream:#{key}", activity.to_json)
|
22
|
+
self.database.ltrim("stream:#{key}", 0, (stream.limit || 10) - 1)
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.get(stream_name)
|
27
|
+
self.database.list_range("stream:#{stream_name}", 0, -1)
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.head(stream_name)
|
31
|
+
self.database.list_index("stream:#{stream_name}", 0)
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
def Storage.lookup(name)
|
36
|
+
@engines ||= {}
|
37
|
+
@engines[name]
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
data/lib/dam/stream.rb
ADDED
@@ -0,0 +1,211 @@
|
|
1
|
+
module Dam
|
2
|
+
|
3
|
+
|
4
|
+
PLACEHOLDER_PATTERN = %r{:[^:@/-]+}
|
5
|
+
|
6
|
+
# provides the basis of the DSL to define a stream
|
7
|
+
class StreamDefinition
|
8
|
+
def initialize
|
9
|
+
@filters = []
|
10
|
+
@limit = 10
|
11
|
+
end
|
12
|
+
|
13
|
+
def limit(amount = nil)
|
14
|
+
amount.nil? ? @amount : @amount = amount
|
15
|
+
end
|
16
|
+
|
17
|
+
def filters
|
18
|
+
@filters
|
19
|
+
end
|
20
|
+
|
21
|
+
def accepts(args = {})
|
22
|
+
@filters << args
|
23
|
+
end
|
24
|
+
|
25
|
+
def params
|
26
|
+
ParamsProxy
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
|
31
|
+
# a class that will allow us to note which param has been used from the placeholders
|
32
|
+
class ParamsProxy
|
33
|
+
attr_reader :key
|
34
|
+
def initialize(key)
|
35
|
+
@key = key
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.[] key
|
39
|
+
ParamsProxy.new(key)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# a templated stream is one which contains placeholders, and thus will only be defined through his instances
|
44
|
+
class TemplatedStream
|
45
|
+
attr_reader :name
|
46
|
+
|
47
|
+
def initialize(name, definition)
|
48
|
+
@name = name
|
49
|
+
@definition = definition
|
50
|
+
|
51
|
+
extract_placeholders!
|
52
|
+
make_glob_pattern!
|
53
|
+
make_regexp!
|
54
|
+
end
|
55
|
+
|
56
|
+
def apply(params)
|
57
|
+
if params.is_a? String
|
58
|
+
name = params
|
59
|
+
params = extract_params(name)
|
60
|
+
else
|
61
|
+
name = replace_placeholders(params)
|
62
|
+
end
|
63
|
+
Stream.new(name, @definition, :params => params)
|
64
|
+
end
|
65
|
+
|
66
|
+
def instances
|
67
|
+
elems = Dam::Storage.database.keys("stream:" + @glob_pattern)
|
68
|
+
elems.each {|elem| streams << apply(elem) }
|
69
|
+
end
|
70
|
+
|
71
|
+
def matches? what
|
72
|
+
what =~ @regexp
|
73
|
+
end
|
74
|
+
|
75
|
+
private
|
76
|
+
|
77
|
+
def replace_placeholders(params)
|
78
|
+
name = @name
|
79
|
+
params.each_pair do |key, value|
|
80
|
+
name = name.gsub(":#{key}", value.to_s)
|
81
|
+
end
|
82
|
+
|
83
|
+
name
|
84
|
+
end
|
85
|
+
|
86
|
+
def extract_params(what)
|
87
|
+
Hash[*@placeholders.collect {|pat| pat[1..-1].to_sym}.zip(@regexp.match(what).captures).flatten]
|
88
|
+
end
|
89
|
+
|
90
|
+
def extract_placeholders!
|
91
|
+
@placeholders = @name.scan(PLACEHOLDER_PATTERN)
|
92
|
+
end
|
93
|
+
|
94
|
+
def make_glob_pattern!
|
95
|
+
@glob_pattern = @placeholders.inject(@name) do |name, placeholder|
|
96
|
+
name.sub(placeholder, "*")
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def make_regexp!
|
101
|
+
@regexp = Regexp.new(@placeholders.inject(@name) do |name, placeholder|
|
102
|
+
name.sub(placeholder, "([^/:-]+)")
|
103
|
+
end)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
class Stream
|
108
|
+
def Stream.lookup(name)
|
109
|
+
@streams ||= {}
|
110
|
+
if @streams.has_key? name
|
111
|
+
@streams[name]
|
112
|
+
else
|
113
|
+
template = @streams.values.find {|stream| stream.respond_to?(:instances) && stream.matches?(name) }
|
114
|
+
template.apply(name) if template
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def Stream.[](name)
|
119
|
+
lookup(name)
|
120
|
+
end
|
121
|
+
|
122
|
+
def Stream.register(name, stream)
|
123
|
+
@streams ||= {}
|
124
|
+
@streams[name] = stream
|
125
|
+
stream
|
126
|
+
end
|
127
|
+
|
128
|
+
def Stream.has_placeholder? string
|
129
|
+
string =~ PLACEHOLDER_PATTERN
|
130
|
+
end
|
131
|
+
|
132
|
+
def Stream.all
|
133
|
+
@streams.values.collect {|stream| stream.respond_to?(:instances) ? stream.instances : stream }.flatten
|
134
|
+
end
|
135
|
+
|
136
|
+
|
137
|
+
attr_reader :name
|
138
|
+
|
139
|
+
def initialize(name, definition, params = {})
|
140
|
+
@name = name
|
141
|
+
@definition = definition
|
142
|
+
@params = params.delete(:params)
|
143
|
+
end
|
144
|
+
|
145
|
+
def limit
|
146
|
+
@definition.limit
|
147
|
+
end
|
148
|
+
|
149
|
+
def filters
|
150
|
+
@definition.filters
|
151
|
+
end
|
152
|
+
|
153
|
+
def all
|
154
|
+
Dam::Storage.get(self.name).collect do |json|
|
155
|
+
Activity.from_json json
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
def first
|
160
|
+
Activity.from_json(Dam::Storage.head(self.name))
|
161
|
+
end
|
162
|
+
|
163
|
+
def matches? activity
|
164
|
+
filters.any? do |filter|
|
165
|
+
return true if filter == :all
|
166
|
+
|
167
|
+
filter.any? do |key, value|
|
168
|
+
attr_match(value, activity.attributes[key.to_s])
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
def instantiate!
|
174
|
+
ensure_exists!
|
175
|
+
self
|
176
|
+
end
|
177
|
+
|
178
|
+
private
|
179
|
+
|
180
|
+
def ensure_exists!
|
181
|
+
if Dam::Storage.database.keys("stream:#{name}").size == 0
|
182
|
+
Dam::Storage.database.push_head("stream:#{name}", 1)
|
183
|
+
Dam::Storage.database.pop_head("stream:#{name}")
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
def attr_match(condition, element)
|
188
|
+
# match a nil element with a nil condition
|
189
|
+
return condition.nil? if element.nil?
|
190
|
+
|
191
|
+
if condition.respond_to? :each_pair
|
192
|
+
condition.all? do |key, value|
|
193
|
+
(element.respond_to?(key) ? element.send(key) : element[key]) == eval_arg(element)
|
194
|
+
end
|
195
|
+
else
|
196
|
+
condition == eval_arg(element)
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
def eval_arg(arg)
|
201
|
+
case arg
|
202
|
+
when ParamsProxy
|
203
|
+
@params[arg.key]
|
204
|
+
when Proc
|
205
|
+
arg.call
|
206
|
+
else
|
207
|
+
arg
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
data/lib/dam/version.rb
ADDED
data/lib/dam.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'dam/activity'
|
2
|
+
require 'dam/storage'
|
3
|
+
require 'dam/stream'
|
4
|
+
|
5
|
+
require 'yajl'
|
6
|
+
|
7
|
+
module Dam
|
8
|
+
|
9
|
+
def self.push(activity)
|
10
|
+
Dam::Stream.all.select {|stream| stream.matches? activity }.each do |stream|
|
11
|
+
Dam::Storage.insert(stream, activity)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.post(type, params = {})
|
16
|
+
act = Activity.new(Dam::ActivityType.lookup(type.to_sym), params)
|
17
|
+
|
18
|
+
act.submit!
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.activity(name, &block)
|
22
|
+
act = Dam::ActivityType.new(name, &block)
|
23
|
+
Dam::ActivityType.register(name, act)
|
24
|
+
act
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.stream(name, &block)
|
28
|
+
definition = StreamDefinition.new
|
29
|
+
definition.instance_eval(&block)
|
30
|
+
|
31
|
+
stream = if Stream.has_placeholder? name
|
32
|
+
TemplatedStream.new(name, definition)
|
33
|
+
else
|
34
|
+
Stream.new(name, definition)
|
35
|
+
end
|
36
|
+
Dam::Stream.register(name, stream)
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/test_helper'
|
2
|
+
|
3
|
+
context "a new activity type" do
|
4
|
+
setup do
|
5
|
+
Dam::activity :comment_posted do
|
6
|
+
author "Some Author"
|
7
|
+
action :post
|
8
|
+
|
9
|
+
comment do
|
10
|
+
{:id => params[:comment]}
|
11
|
+
end
|
12
|
+
|
13
|
+
project do
|
14
|
+
{:id => params[:project], :some_other_property => true }
|
15
|
+
end
|
16
|
+
published { Date.today }
|
17
|
+
text { "a comment has been posted" }
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
topic.kind_of(Dam::ActivityType)
|
22
|
+
|
23
|
+
asserts("is registered") { topic }.equals(Dam::ActivityType.lookup(:comment_posted))
|
24
|
+
asserts("has a name") { topic.name }.equals(:comment_posted)
|
25
|
+
|
26
|
+
asserts("has a static author") { topic.attributes["author"] }.equals("Some Author")
|
27
|
+
asserts("has a static action") { topic.attributes["action"] }.equals(:post)
|
28
|
+
|
29
|
+
asserts("has a comment proc") { topic.attributes["comment"] }.kind_of(Proc)
|
30
|
+
asserts("has a project proc") { topic.attributes["project"] }.kind_of(Proc)
|
31
|
+
|
32
|
+
asserts("has a published proc") { topic.attributes["published"] }.kind_of(Proc)
|
33
|
+
asserts("has a text proc") { topic.attributes["text"] }.kind_of(Proc)
|
34
|
+
|
35
|
+
context "can be instantiated" do
|
36
|
+
setup do
|
37
|
+
topic.apply({:comment => "ab3d", :project => "xyz" })
|
38
|
+
end
|
39
|
+
|
40
|
+
topic.kind_of(Dam::Activity)
|
41
|
+
|
42
|
+
asserts("the author has been evaluated") { topic.author }.equals("Some Author")
|
43
|
+
asserts("the action has been evaluated") { topic.action }.equals(:post)
|
44
|
+
asserts("the published date has been evaluated") { topic.published }.kind_of(Date)
|
45
|
+
asserts("the comment has been evaluated") { topic.comment }.equals({:id => "ab3d"})
|
46
|
+
asserts("the project has been evaluated") { topic.project }.equals({:id => "xyz", :some_other_property => true})
|
47
|
+
asserts("the text has been evaluated") { topic.text }.equals("a comment has been posted")
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
|
52
|
+
context "an activity without params" do
|
53
|
+
setup do
|
54
|
+
Dam.activity :no_params do
|
55
|
+
action :post
|
56
|
+
author "bob"
|
57
|
+
some_param 123
|
58
|
+
end
|
59
|
+
end
|
60
|
+
topic.kind_of(Dam::ActivityType)
|
61
|
+
asserts("can be applied without params") { topic.apply }.kind_of(Dam::Activity)
|
62
|
+
end
|
data/test/stream_test.rb
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/test_helper'
|
2
|
+
|
3
|
+
|
4
|
+
# Define some activities to be used in the tests
|
5
|
+
Dam.activity :comment do
|
6
|
+
action "post"
|
7
|
+
author { { "name" => params[:author] } }
|
8
|
+
end
|
9
|
+
|
10
|
+
Dam.activity :edit do
|
11
|
+
action :edit
|
12
|
+
end
|
13
|
+
|
14
|
+
|
15
|
+
context "a stream which filters the latest 25 activities" do
|
16
|
+
setup do
|
17
|
+
Dam::stream :all do
|
18
|
+
limit 25
|
19
|
+
accepts :all
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
topic.kind_of(Dam::Stream)
|
24
|
+
topic.equals(Dam::Stream[:all])
|
25
|
+
asserts("has one filter") { topic.filters.size }.equals(1)
|
26
|
+
asserts("has a limit of 25") { topic.limit }.equals(25)
|
27
|
+
|
28
|
+
asserts("accepts an activity") { topic.matches? Dam::Activity[:comment].apply({:author => "test"})}
|
29
|
+
end
|
30
|
+
|
31
|
+
context "a stream which only accepts edits" do
|
32
|
+
setup do
|
33
|
+
Dam::stream :edit_actions do
|
34
|
+
accepts :action => :edit
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
topic.kind_of(Dam::Stream)
|
39
|
+
asserts("rejects a post action") { !topic.matches? Dam::Activity[:comment].apply({:author => "test"})}
|
40
|
+
asserts("accepts an edit") { topic.matches? Dam::Activity[:edit].apply}
|
41
|
+
end
|
42
|
+
|
43
|
+
context "a stream with a complex object filter" do
|
44
|
+
setup do
|
45
|
+
Dam::stream :only_from_bob do
|
46
|
+
accepts :author => { "name" => "bob" }
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
topic.kind_of(Dam::Stream)
|
51
|
+
asserts("accepts a valid activity") { topic.matches? Dam::Activity[:comment].apply({:author => "bob"})}
|
52
|
+
asserts("rejects an activity with a different value") { !topic.matches? Dam::Activity[:comment].apply({:author => "not bob"})}
|
53
|
+
asserts("reject an activity which doesn't have the attribute") { !topic.matches? Dam::Activity[:edit].apply }
|
54
|
+
end
|
55
|
+
|
56
|
+
context "post an activity" do
|
57
|
+
setup do
|
58
|
+
Dam::Storage.clear!
|
59
|
+
Dam.stream :all do
|
60
|
+
accepts :all
|
61
|
+
end
|
62
|
+
|
63
|
+
act = Dam::Activity[:comment].apply(:author => "bob")
|
64
|
+
act.post!
|
65
|
+
act
|
66
|
+
end
|
67
|
+
|
68
|
+
asserts("the stream has one element") { Dam::Stream[:all].all.size }.equals(1)
|
69
|
+
topic.equals(Dam::Stream[:all].first)
|
70
|
+
end
|
71
|
+
|
72
|
+
context "post multiple activities" do
|
73
|
+
setup do
|
74
|
+
Dam::Storage.clear!
|
75
|
+
Dam.stream :only_2 do
|
76
|
+
accepts :all
|
77
|
+
limit 2
|
78
|
+
end
|
79
|
+
3.times {|i| Dam::Activity[:comment].apply(:author => "bob_#{i}").post! }
|
80
|
+
end
|
81
|
+
|
82
|
+
asserts("the stream has been limited to 2 elements") { Dam::Stream[:only_2].all.size }.equals(2)
|
83
|
+
asserts("the first one is the last to be entered") { Dam::Stream[:only_2].first.author }.equals({"name" => "bob_2"})
|
84
|
+
end
|
85
|
+
|
86
|
+
|
87
|
+
context "a parameterized stream" do
|
88
|
+
setup do
|
89
|
+
Dam::Storage.clear!
|
90
|
+
Dam.stream "comments/:author" do
|
91
|
+
accepts :author => { "name" => params[:author] }
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
topic.kind_of(Dam::TemplatedStream)
|
96
|
+
asserts("has no actual instances at first") { topic.instances.size }.equals(0)
|
97
|
+
|
98
|
+
context "with an instance" do
|
99
|
+
setup do
|
100
|
+
Dam::Stream["comments/bob"].instantiate!
|
101
|
+
end
|
102
|
+
|
103
|
+
topic.kind_of(Dam::Stream)
|
104
|
+
asserts("matches a valid activity") { topic.matches? Dam::Activity[:comment].apply(:author => "bob") }
|
105
|
+
end
|
106
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
|
3
|
+
require 'riot'
|
4
|
+
|
5
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), "..", "lib"))
|
6
|
+
|
7
|
+
require 'dam'
|
8
|
+
|
9
|
+
DATABASE = 15 # change this if you already use database #15 for something else
|
10
|
+
|
11
|
+
Dam::Storage.database = Redis.new(:db => DATABASE) # change this to your running redis server
|
12
|
+
|
13
|
+
module Dam
|
14
|
+
class Storage
|
15
|
+
def self.clear!
|
16
|
+
@database.keys("*").each {|k| @database.delete k}
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
metadata
ADDED
@@ -0,0 +1,107 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: dam
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Jean-Philippe Bougie
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-11-20 00:00:00 -05:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: redis
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: "0"
|
24
|
+
version:
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: yajl-ruby
|
27
|
+
type: :runtime
|
28
|
+
version_requirement:
|
29
|
+
version_requirements: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: "0"
|
34
|
+
version:
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: jeweler
|
37
|
+
type: :development
|
38
|
+
version_requirement:
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: "0"
|
44
|
+
version:
|
45
|
+
- !ruby/object:Gem::Dependency
|
46
|
+
name: riot
|
47
|
+
type: :development
|
48
|
+
version_requirement:
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: "0"
|
54
|
+
version:
|
55
|
+
description: ""
|
56
|
+
email: jp.bougie@gmail.com
|
57
|
+
executables: []
|
58
|
+
|
59
|
+
extensions: []
|
60
|
+
|
61
|
+
extra_rdoc_files:
|
62
|
+
- LICENSE
|
63
|
+
- README.markdown
|
64
|
+
files:
|
65
|
+
- LICENSE
|
66
|
+
- README.markdown
|
67
|
+
- Rakefile
|
68
|
+
- lib/dam.rb
|
69
|
+
- lib/dam/activity.rb
|
70
|
+
- lib/dam/storage.rb
|
71
|
+
- lib/dam/stream.rb
|
72
|
+
- lib/dam/version.rb
|
73
|
+
- test/activity_test.rb
|
74
|
+
- test/stream_test.rb
|
75
|
+
- test/test_helper.rb
|
76
|
+
has_rdoc: true
|
77
|
+
homepage: http://github.com/jpbougie/dam
|
78
|
+
licenses: []
|
79
|
+
|
80
|
+
post_install_message:
|
81
|
+
rdoc_options:
|
82
|
+
- --charset=UTF-8
|
83
|
+
require_paths:
|
84
|
+
- lib
|
85
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: "0"
|
90
|
+
version:
|
91
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
92
|
+
requirements:
|
93
|
+
- - ">="
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: "0"
|
96
|
+
version:
|
97
|
+
requirements: []
|
98
|
+
|
99
|
+
rubyforge_project:
|
100
|
+
rubygems_version: 1.3.5
|
101
|
+
signing_key:
|
102
|
+
specification_version: 3
|
103
|
+
summary: An activity stream framework for Ruby
|
104
|
+
test_files:
|
105
|
+
- test/activity_test.rb
|
106
|
+
- test/stream_test.rb
|
107
|
+
- test/test_helper.rb
|