chalk_dust 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ spec/db.sqlite3
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
data/.travis.yml ADDED
@@ -0,0 +1,6 @@
1
+ language: ruby
2
+ rvm:
3
+ - '1.9.2'
4
+ - '1.9.3'
5
+ - jruby-19mode
6
+ - rbx-19mode
data/Gemfile ADDED
@@ -0,0 +1,14 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in chalk_dust.gemspec
4
+ gemspec
5
+
6
+ # These are development dependencies
7
+
8
+ platforms :jruby do
9
+ gem "activerecord-jdbcsqlite3-adapter"
10
+ end
11
+
12
+ platforms :ruby do
13
+ gem "sqlite3-ruby"
14
+ end
data/Guardfile ADDED
@@ -0,0 +1,7 @@
1
+ guard 'rspec' do
2
+ watch(%r{^spec/.+_spec\.rb$})
3
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
4
+ watch('spec/spec_helper.rb') { "spec" }
5
+ watch(%r{^spec/support/(.+)\.rb$}) { "spec" }
6
+ end
7
+
data/README.md ADDED
@@ -0,0 +1,124 @@
1
+ # ChalkDust
2
+
3
+ ChalkDust can be used to build activty feeds such as followings and friendships
4
+ by allowing models to subscribe to activity feeds published by other models.
5
+
6
+ Every time an activity occurs it is copied to all subscribers of the target of
7
+ that activity. This creates an activty feed per subscriber (more data) which
8
+ scales better and allows additional features such as feed to delete/hide
9
+ activity items.
10
+
11
+ [![Code Climate](https://codeclimate.com/github/krisleech/chalk-dust.png)](https://codeclimate.com/github/krisleech/chalk-dust)
12
+ [![Build Status](https://travis-ci.org/krisleech/chalk-dust.png)](https://travis-ci.org/krisleech/chalk-dust)
13
+
14
+ ## Installation
15
+
16
+ Add this line to your application's Gemfile:
17
+
18
+ gem 'chalk_dust'
19
+
20
+ ## Usage
21
+
22
+ ### Subscribing
23
+
24
+ A subscription connects two objects, e.g "Sam" follows "My first blog post"
25
+
26
+ ```ruby
27
+ ChalkDust.subscribe(user, :to => post)
28
+ ```
29
+
30
+ ```ruby
31
+ ChalkDust.subscribers_of(post) # => [user, post]
32
+ ```
33
+
34
+ #### Self-subscribing
35
+
36
+ Typically you will want to self-subscribe models on creation to themseleves so
37
+ an activity feed is built for the model itself.
38
+
39
+ ```ruby
40
+ ChalkDust.self_subscribe(user)
41
+ # or
42
+ ChalkDust.subscribe(user, :to => user)
43
+ ```
44
+
45
+ #### Undirected subscriptions
46
+
47
+ All subscriptions are directed. You can create a two way subscription, e.g a
48
+ friendship, as follows:
49
+
50
+ ```ruby
51
+ ChalkDust.subscribe(bob, :to => alice, :undirected => true)
52
+ # or
53
+ ChalkDust.subscribe(bob, :to => alice)
54
+ ChalkDust.subscribe(alice, :to => bob)
55
+ ```
56
+
57
+ ### Publishing activity
58
+
59
+ Describes an event where X (performer) did Y (activity) to Z (target).
60
+
61
+ ```ruby
62
+ ChalkDust.publish_event(user, 'added', comment)
63
+ ```
64
+
65
+ If the activity should be published to the feed of an object other
66
+ than the target then it can be either be passed as an additional argument or
67
+ provided as a method called `activity_root`.
68
+
69
+ ```ruby
70
+ ChalkDust.publish_event(user, 'added', comment, :root => comment.post)
71
+ ```
72
+
73
+ or
74
+
75
+ (TODO)
76
+ ```ruby
77
+ class Comment < ActiveRecord::Base
78
+ belongs_to :post
79
+
80
+ def activity_root
81
+ post
82
+ end
83
+ end
84
+ ```
85
+
86
+ ### Activity Feeds
87
+
88
+ ```ruby
89
+ ChalkDust.activity_feed_for(user, :since => 2.weeks.ago)
90
+ ChalkDust.activity_feed_for(post, :since => 2.weeks.ago)
91
+ ```
92
+
93
+ ## Contributing
94
+
95
+ Contributions welcome, please fork and send a pull request.
96
+
97
+ ## Thanks
98
+
99
+ Thanks to Igor @ Lifetrip Limited for allowing this code to be open sourced.
100
+
101
+ ## License
102
+
103
+ Copyright (c) 2013 Lifetrip Limited
104
+
105
+ MIT License
106
+
107
+ Permission is hereby granted, free of charge, to any person obtaining
108
+ a copy of this software and associated documentation files (the
109
+ "Software"), to deal in the Software without restriction, including
110
+ without limitation the rights to use, copy, modify, merge, publish,
111
+ distribute, sublicense, and/or sell copies of the Software, and to
112
+ permit persons to whom the Software is furnished to do so, subject to
113
+ the following conditions:
114
+
115
+ The above copyright notice and this permission notice shall be
116
+ included in all copies or substantial portions of the Software.
117
+
118
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
119
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
120
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
121
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
122
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
123
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
124
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
3
+
4
+ require 'rspec/core/rake_task'
5
+
6
+ RSpec::Core::RakeTask.new
7
+
8
+ task :default => :spec
@@ -0,0 +1,25 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/chalk_dust/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Kris Leech"]
6
+ gem.email = ["kris.leech@gmail.com"]
7
+ gem.description = %q{Subscribe to and build activity feeds for your models, for example followings and friendships}
8
+ gem.summary = %q{Subscribe to and build activity feeds for your models, for example followings and friendships}
9
+ gem.homepage = "https://github.com/krisleech/chalk-dust"
10
+
11
+ gem.files = `git ls-files`.split($\)
12
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
13
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
14
+ gem.name = "chalk_dust"
15
+ gem.require_paths = ["lib"]
16
+ gem.version = ChalkDust::VERSION
17
+
18
+ gem.add_dependency "activerecord", "~> 3.0"
19
+
20
+ gem.add_development_dependency "bundler"
21
+ gem.add_development_dependency "rspec", "~> 2.3"
22
+ gem.add_development_dependency 'rake'
23
+ gem.add_development_dependency 'simplecov'
24
+ gem.add_development_dependency 'guard-rspec'
25
+ end
data/lib/chalk_dust.rb ADDED
@@ -0,0 +1,63 @@
1
+ require "chalk_dust/version"
2
+
3
+ module ChalkDust
4
+ class Connection < ActiveRecord::Base
5
+ belongs_to :publisher, :polymorphic => true
6
+ belongs_to :subscriber, :polymorphic => true
7
+
8
+ def self.subscribers_of(publisher)
9
+ where(:publisher_id => publisher.id,
10
+ :publisher_type => publisher.class.to_s).map(&:subscriber)
11
+ end
12
+ end
13
+
14
+ class ActivityItem < ActiveRecord::Base
15
+ belongs_to :performer, :polymorphic => true
16
+ belongs_to :target, :polymorphic => true
17
+ belongs_to :owner, :polymorphic => true
18
+
19
+ validates :event, :presence => true
20
+
21
+ def self.for_owner(owner)
22
+ where(:owner_id => owner.id,
23
+ :owner_type => owner.class.to_s)
24
+ end
25
+
26
+ def self.since(time)
27
+ where("created_at >= ?", time)
28
+ end
29
+ end
30
+
31
+ def self.subscribe(subscriber, options)
32
+ publisher = options.fetch(:to)
33
+ undirected = options.fetch(:undirected, false)
34
+ Connection.create(:subscriber => subscriber, :publisher => publisher)
35
+ Connection.create(:subscriber => publisher, :publisher => subscriber) if undirected
36
+ end
37
+
38
+ def self.subscribers_of(publisher)
39
+ Connection.subscribers_of(publisher)
40
+ end
41
+
42
+ def self.self_subscribe(publisher_subscriber)
43
+ subscribe(publisher_subscriber, :to => publisher_subscriber)
44
+ end
45
+
46
+ # publishes an event where X (performer) did Y (event) to Z (target) to every
47
+ # subscriber of the target
48
+ def self.publish_event(performer, event, target, options = {})
49
+ root_publisher = options.fetch(:root, target)
50
+ subscribers_of(root_publisher).map do |subscriber|
51
+ ActivityItem.create(:performer => performer,
52
+ :event => event,
53
+ :target => target,
54
+ :owner => subscriber)
55
+ end
56
+ end
57
+
58
+ def self.activity_feed_for(subscriber, options = {})
59
+ activity_items = ActivityItem.for_owner(subscriber)
60
+ activity_items = activity_items.since(options[:since]) if options[:since].present?
61
+ activity_items
62
+ end
63
+ end
@@ -0,0 +1,3 @@
1
+ module ChalkDust
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,55 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'activity feeds' do
4
+ before(:each) do
5
+ ChalkDust::ActivityItem.delete_all
6
+ end
7
+
8
+ describe '.activity_feed_for' do
9
+ it 'returns activity feed for given object' do
10
+ kris = User.create!
11
+ lindsey = User.create!
12
+ hallie = User.create!
13
+ post = Post.create!
14
+ comment = Comment.create!
15
+
16
+ activity_item_1 = ChalkDust::ActivityItem.create(:performer => kris,
17
+ :event => 'editted',
18
+ :target => post,
19
+ :owner => hallie)
20
+
21
+ activity_item_2 = ChalkDust::ActivityItem.create(:performer => lindsey,
22
+ :event => 'added',
23
+ :target => comment,
24
+ :owner => hallie)
25
+
26
+ activity_items = ChalkDust.activity_feed_for(hallie)
27
+ activity_items.should == [activity_item_1, activity_item_2]
28
+ end
29
+
30
+ describe 'options' do
31
+ it ':since limits to activities created since the given date' do
32
+ kris = User.create!
33
+ lindsey = User.create!
34
+ hallie = User.create!
35
+ post = Post.create!
36
+ comment = Comment.create!
37
+
38
+ activity_item_1 = ChalkDust::ActivityItem.create(:performer => kris,
39
+ :event => 'editted',
40
+ :target => post,
41
+ :owner => hallie,
42
+ :created_at => 2.months.ago)
43
+
44
+ activity_item_2 = ChalkDust::ActivityItem.create(:performer => lindsey,
45
+ :event => 'added',
46
+ :target => comment,
47
+ :owner => hallie)
48
+
49
+ activity_items = ChalkDust.activity_feed_for(hallie, :since => Time.now - 1.week)
50
+ activity_items.should == [activity_item_2]
51
+ end
52
+ end
53
+ end
54
+ end
55
+
@@ -0,0 +1,62 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'publishing' do
4
+ before(:each) do
5
+ ChalkDust::Connection.delete_all
6
+ ChalkDust::ActivityItem.delete_all
7
+ end
8
+
9
+ describe '.publish_event' do
10
+ it 'creates an event for every subscriber' do
11
+ kris = User.create!
12
+ lindsey = User.create!
13
+ hallie = User.create!
14
+ post = Post.create!
15
+
16
+ ChalkDust::Connection.create(:subscriber => kris, :publisher => post)
17
+ ChalkDust::Connection.create(:subscriber => lindsey, :publisher => post)
18
+
19
+ activity_items = ChalkDust.publish_event(kris, 'editted', post)
20
+
21
+ activity_items.size.should == 2
22
+
23
+ activity_item = activity_items.first
24
+ activity_item.performer.should == kris
25
+ activity_item.event.should == 'editted'
26
+ activity_item.target.should == post
27
+ activity_item.owner.should == kris
28
+
29
+ activity_item = activity_items.last
30
+ activity_item.performer.should == kris
31
+ activity_item.event.should == 'editted'
32
+ activity_item.target.should == post
33
+ activity_item.owner.should == lindsey
34
+ end
35
+
36
+ describe 'options' do
37
+ describe ':root' do
38
+ it 'sets the root object of the target' do
39
+ kris = User.create!
40
+ lindsey = User.create!
41
+ post = Post.create!
42
+ comment = Comment.create!(:post => post)
43
+
44
+ ChalkDust::Connection.create(:subscriber => lindsey, :publisher => post)
45
+
46
+ activity_items = ChalkDust.publish_event(kris, 'added', comment, :root => comment.post)
47
+
48
+ activity_items.size.should == 1
49
+
50
+ activity_item = activity_items.first
51
+ activity_item.performer.should == kris
52
+ activity_item.event.should == 'added'
53
+ activity_item.target.should == comment
54
+ activity_item.owner.should == lindsey
55
+ end
56
+ end
57
+ end
58
+
59
+ # pending 'target root can be set by the `activity_root` method on the target'
60
+ end
61
+ end
62
+
@@ -0,0 +1,58 @@
1
+ require 'spec_helper'
2
+
3
+ describe ChalkDust do
4
+ before(:each) { ChalkDust::Connection.delete_all }
5
+
6
+ describe 'subscribing' do
7
+ it '.subscribe connects two objects' do
8
+ user = User.create!
9
+ post = Post.create!
10
+
11
+ ChalkDust.subscribe(user, :to => post)
12
+
13
+ connection = ChalkDust::Connection.first
14
+ connection.subscriber.should == user
15
+ connection.publisher.should == post
16
+ end
17
+
18
+ it '.self_subscribe connects object to itself' do
19
+ user = User.create!
20
+
21
+ ChalkDust.self_subscribe(user)
22
+
23
+ connection = ChalkDust::Connection.first
24
+ connection.subscriber.should == user
25
+ connection.publisher.should == user
26
+ end
27
+
28
+ describe 'options' do
29
+ it ':undirected subscribes in both directions' do
30
+ bob = User.create!
31
+ alice = User.create!
32
+
33
+ ChalkDust.subscribe(bob, :to => alice, :undirected => true)
34
+
35
+ ChalkDust::Connection.count.should == 2
36
+
37
+ connection_1 = ChalkDust::Connection.first
38
+ connection_1.subscriber.should == bob
39
+ connection_1.publisher.should == alice
40
+
41
+ connection_2 = ChalkDust::Connection.last
42
+ connection_2.subscriber.should == alice
43
+ connection_2.publisher.should == bob
44
+ end
45
+ end
46
+ end
47
+
48
+ describe 'fetching subscriptions' do
49
+ it '.subscriptions_of returns subscribers to given object' do
50
+ user = User.create!
51
+ post = Post.create!
52
+
53
+ ChalkDust::Connection.create!(:subscriber => user, :publisher => post)
54
+
55
+ ChalkDust.subscribers_of(post).should == [user]
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,21 @@
1
+ require 'simplecov'
2
+ SimpleCov.start
3
+
4
+ require 'active_record'
5
+ require 'chalk_dust'
6
+
7
+ adapter = RUBY_PLATFORM == "java" ? 'jdbcsqlite3' : 'sqlite3'
8
+
9
+ ActiveRecord::Base.establish_connection(:adapter => adapter,
10
+ :database => File.dirname(__FILE__) + "/db.sqlite3")
11
+
12
+ load File.dirname(__FILE__) + '/support/schema.rb'
13
+
14
+ require 'support/models'
15
+
16
+ RSpec.configure do |config|
17
+ config.treat_symbols_as_metadata_keys_with_true_values = true
18
+ config.run_all_when_everything_filtered = true
19
+ config.filter_run :focus
20
+ config.order = 'random'
21
+ end
@@ -0,0 +1,12 @@
1
+ class User < ActiveRecord::Base
2
+ has_many :comments
3
+ end
4
+
5
+ class Post < ActiveRecord::Base
6
+ has_many :comments
7
+ end
8
+
9
+ class Comment < ActiveRecord::Base
10
+ belongs_to :post
11
+ belongs_to :user
12
+ end
@@ -0,0 +1,48 @@
1
+ ActiveRecord::Schema.define do
2
+ self.verbose = false
3
+
4
+ create_table :users, :force => true do |t|
5
+ t.integer :id
6
+ t.string :name
7
+ t.string :email
8
+ t.timestamps
9
+ end
10
+
11
+ create_table :posts, :force => true do |t|
12
+ t.integer :id
13
+ t.string :title
14
+ t.string :body
15
+ t.timestamps
16
+ end
17
+
18
+ create_table :comments, :force => true do |t|
19
+ t.integer :id
20
+ t.integer :user_id
21
+ t.integer :post_id
22
+ t.string :body
23
+ t.timestamps
24
+ end
25
+
26
+ create_table :connections, :force => true do |t|
27
+ t.integer :subscriber_id
28
+ t.string :subscriber_type
29
+ t.integer :publisher_id
30
+ t.string :publisher_type
31
+ t.timestamps
32
+ end
33
+
34
+ create_table :activity_items, :force => true do |t|
35
+ t.integer :performer_id
36
+ t.string :performer_type
37
+
38
+ t.string :event
39
+
40
+ t.integer :target_id
41
+ t.string :target_type
42
+
43
+ t.integer :owner_id
44
+ t.string :owner_type
45
+
46
+ t.timestamps
47
+ end
48
+ end
metadata ADDED
@@ -0,0 +1,171 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: chalk_dust
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Kris Leech
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-04-10 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: activerecord
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '3.0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '3.0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: bundler
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: rspec
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ~>
52
+ - !ruby/object:Gem::Version
53
+ version: '2.3'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: '2.3'
62
+ - !ruby/object:Gem::Dependency
63
+ name: rake
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ - !ruby/object:Gem::Dependency
79
+ name: simplecov
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ! '>='
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ type: :development
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ - !ruby/object:Gem::Dependency
95
+ name: guard-rspec
96
+ requirement: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ! '>='
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ type: :development
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ! '>='
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ description: Subscribe to and build activity feeds for your models, for example followings
111
+ and friendships
112
+ email:
113
+ - kris.leech@gmail.com
114
+ executables: []
115
+ extensions: []
116
+ extra_rdoc_files: []
117
+ files:
118
+ - .gitignore
119
+ - .rspec
120
+ - .travis.yml
121
+ - Gemfile
122
+ - Guardfile
123
+ - README.md
124
+ - Rakefile
125
+ - chalk_dust.gemspec
126
+ - lib/chalk_dust.rb
127
+ - lib/chalk_dust/version.rb
128
+ - spec/lib/chalk_dust/activity_feeds_spec.rb
129
+ - spec/lib/chalk_dust/publishing_spec.rb
130
+ - spec/lib/chalk_dust/subscribing_spec.rb
131
+ - spec/spec_helper.rb
132
+ - spec/support/models.rb
133
+ - spec/support/schema.rb
134
+ homepage: https://github.com/krisleech/chalk-dust
135
+ licenses: []
136
+ post_install_message:
137
+ rdoc_options: []
138
+ require_paths:
139
+ - lib
140
+ required_ruby_version: !ruby/object:Gem::Requirement
141
+ none: false
142
+ requirements:
143
+ - - ! '>='
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ segments:
147
+ - 0
148
+ hash: -4022978035697313999
149
+ required_rubygems_version: !ruby/object:Gem::Requirement
150
+ none: false
151
+ requirements:
152
+ - - ! '>='
153
+ - !ruby/object:Gem::Version
154
+ version: '0'
155
+ segments:
156
+ - 0
157
+ hash: -4022978035697313999
158
+ requirements: []
159
+ rubyforge_project:
160
+ rubygems_version: 1.8.23
161
+ signing_key:
162
+ specification_version: 3
163
+ summary: Subscribe to and build activity feeds for your models, for example followings
164
+ and friendships
165
+ test_files:
166
+ - spec/lib/chalk_dust/activity_feeds_spec.rb
167
+ - spec/lib/chalk_dust/publishing_spec.rb
168
+ - spec/lib/chalk_dust/subscribing_spec.rb
169
+ - spec/spec_helper.rb
170
+ - spec/support/models.rb
171
+ - spec/support/schema.rb