dam 0.1.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/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
|