bustle 0.0.1 → 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.
Files changed (42) hide show
  1. data/.gitignore +2 -0
  2. data/.rspec +1 -0
  3. data/.travis.yml +4 -0
  4. data/README.md +37 -40
  5. data/bustle.gemspec +9 -1
  6. data/lib/bustle/activities.rb +25 -0
  7. data/lib/bustle/concerns/by_publisher.rb +11 -0
  8. data/lib/bustle/concerns/filterable.rb +11 -0
  9. data/lib/bustle/concerns/for_subscriber.rb +11 -0
  10. data/lib/bustle/concerns/resource_collection.rb +25 -0
  11. data/lib/bustle/models/activity.rb +4 -0
  12. data/lib/bustle/models/publisher.rb +11 -0
  13. data/lib/bustle/models/subscriber.rb +7 -0
  14. data/lib/bustle/models/subscription.rb +4 -0
  15. data/lib/bustle/publishers.rb +7 -0
  16. data/lib/bustle/storage/active_record/activity.rb +5 -0
  17. data/lib/bustle/storage/active_record/publisher.rb +5 -0
  18. data/lib/bustle/storage/active_record/subscriber.rb +5 -0
  19. data/lib/bustle/storage/active_record/subscription.rb +5 -0
  20. data/lib/bustle/storage/active_record.rb +5 -0
  21. data/lib/bustle/subscribers.rb +7 -0
  22. data/lib/bustle/subscriptions.rb +29 -0
  23. data/lib/bustle/version.rb +1 -1
  24. data/lib/bustle.rb +30 -0
  25. data/specs/bustle/activities_spec.rb +65 -0
  26. data/specs/bustle/models/activity_spec.rb +9 -0
  27. data/specs/bustle/models/publisher_spec.rb +17 -0
  28. data/specs/bustle/models/subscriber_spec.rb +11 -0
  29. data/specs/bustle/models/subscription_spec.rb +9 -0
  30. data/specs/bustle/publishers_spec.rb +8 -0
  31. data/specs/bustle/storage/active_record_spec.rb +23 -0
  32. data/specs/bustle/subscribers_spec.rb +8 -0
  33. data/specs/bustle/subscriptions_spec.rb +76 -0
  34. data/specs/bustle_spec.rb +12 -0
  35. data/specs/spec_helper.rb +35 -0
  36. data/specs/support/active_record_tables.rb +45 -0
  37. data/specs/support/shared_context/app_resources.rb +11 -0
  38. data/specs/support/shared_examples/has_activities.rb +17 -0
  39. data/specs/support/shared_examples/model.rb +38 -0
  40. data/specs/support/shared_examples/resource_collection.rb +24 -0
  41. metadata +152 -6
  42. data/lib/specs/spec_helper.rb +0 -8
data/.gitignore CHANGED
@@ -1,3 +1,5 @@
1
+ .DS_Store
2
+ ._*
1
3
  *.gem
2
4
  *.rbc
3
5
  .bundle
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --colour
data/.travis.yml ADDED
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.2
4
+ - 1.9.3
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # Bustle
1
+ # Bustle [![Build Status](https://secure.travis-ci.org/fredwu/bustle.png?branch=master)](http://travis-ci.org/fredwu/bustle) [![Dependency Status](https://gemnasium.com/fredwu/bustle.png)](https://gemnasium.com/fredwu/bustle) [![Code Climate](https://codeclimate.com/badge.png)](https://codeclimate.com/github/fredwu/bustle)
2
2
 
3
3
  Activities recording and retrieving using a simple Pub/Sub architecture.
4
4
 
@@ -7,6 +7,14 @@ The typical use cases are:
7
7
  - Timeline (e.g. tracking activities such as posting and commenting for users)
8
8
  - Logging
9
9
 
10
+ The advantages of Bustle are:
11
+
12
+ - It is lightweight and simple to use
13
+ - It is largely self-contained and separated from you core app logic
14
+ - It works nicely with ActiveRecord out of box
15
+ - It can be extended to use with other databases
16
+ - It has full unit test coverage
17
+
10
18
  ## Installation
11
19
 
12
20
  Add this line to your application's Gemfile:
@@ -23,11 +31,6 @@ First of all, you will need to configure Bustle. If you are using Rails, you can
23
31
 
24
32
  ```ruby
25
33
  Bustle.config do |c|
26
- # Specify an intepreter strategy for intepreting app data
27
- # i.e. what your application uses for persistence
28
- # Bustle ships with an ActiveRecord intepreter strategy
29
- c.intepreter = Bustle::Intepreter::ActiveRecord
30
-
31
34
  # Specify a storage strategy for storing activities
32
35
  # Bustle ships with an ActiveRecord storage strategy
33
36
  c.storage = Bustle::Storage::ActiveRecord
@@ -40,34 +43,34 @@ For ActiveRecord, you will need the following migration file:
40
43
  class CreateBustleTables < ActiveRecord::Migration
41
44
  def change
42
45
  create_table :bustle_activities do |t|
43
- t.string :reference_class
44
- t.integer :reference_id
46
+ t.string :resource_class
47
+ t.integer :resource_id
45
48
  t.string :action
46
49
  t.integer :publisher_id, :null => false
47
50
  t.timestamps
48
51
  end
49
52
 
50
53
  create_table :bustle_publishers do |t|
51
- t.string :reference_class, :null => false
52
- t.integer :reference_id, :null => false
54
+ t.string :resource_class, :null => false
55
+ t.integer :resource_id, :null => false
53
56
  t.timestamps
54
57
  end
55
58
 
56
59
  create_table :bustle_subscribers do |t|
57
- t.string :reference_class, :null => false
58
- t.integer :reference_id, :null => false
60
+ t.string :resource_class, :null => false
61
+ t.integer :resource_id, :null => false
59
62
  t.timestamps
60
63
  end
61
64
 
62
65
  create_table :bustle_subscriptions do |t|
63
66
  t.integer :publisher_id, :null => false
64
- t.string :subscriber_id, :null => false
67
+ t.integer :subscriber_id, :null => false
65
68
  t.timestamps
66
69
  end
67
70
 
68
71
  add_index :bustle_activities, :publisher_id
69
- add_index :bustle_publishers, [:reference_class, :reference_id], :unique => true
70
- add_index :bustle_subscribers, [:reference_class, :reference_id], :unique => true
72
+ add_index :bustle_publishers, [:resource_class, :resource_id], :unique => true
73
+ add_index :bustle_subscribers, [:resource_class, :resource_id], :unique => true
71
74
  add_index :bustle_subscriptions, :publisher_id
72
75
  add_index :bustle_subscriptions, :subscriber_id
73
76
  add_index :bustle_subscriptions, [:publisher_id, :subscriber_id], :unique => true
@@ -116,19 +119,19 @@ Bustle::Publishers.add post
116
119
  Bustle::Subscriptions.add bustle_publisher, bustle_subscriber
117
120
 
118
121
  # example
119
- publisher = Bustle::Publishers.find(Post.first)
120
- subscriber = Bustle::Subscribers.find(User.first)
122
+ publisher = Bustle::Publishers.get(Post.first)
123
+ subscriber = Bustle::Subscribers.get(User.first)
121
124
  Bustle::Subscriptions.add publisher, subscriber
122
125
  ```
123
126
 
124
127
  #### Find a Subscriber/Publisher/Subscription
125
128
 
126
129
  ```ruby
127
- Bustle::Subscribers.find subscriber
128
- Bustle::Publishers.find publisher
129
- Bustle::Subscriptions.find bustle_publisher, bustle_subscriber # => Bustle::Subscription
130
- Bustle::Subscriptions.find bustle_publisher # => an array of Bustle::Subscription for the publisher
131
- Bustle::Subscriptions.find bustle_subscriber # => an array of Bustle::Subscription for the subscriber
130
+ Bustle::Subscribers.get subscriber
131
+ Bustle::Publishers.get publisher
132
+ Bustle::Subscriptions.get bustle_publisher, bustle_subscriber # => Bustle::Subscription
133
+ Bustle::Subscriptions.by bustle_publisher # => an array of Bustle::Subscription by the publisher
134
+ Bustle::Subscriptions.for bustle_subscriber # => an array of Bustle::Subscription for the subscriber
132
135
  ```
133
136
 
134
137
  #### Remove a Subscriber/Publisher/Subscription
@@ -142,9 +145,9 @@ Bustle::Subscriptions.remove bustle_publisher, bustle_subscriber
142
145
  Or:
143
146
 
144
147
  ```ruby
145
- Bustle::Subscribers.find(subscriber).destroy
146
- Bustle::Publishers.find(publisher).destroy
147
- Bustle::Subscriptions.find(bustle_publisher, bustle_subscriber).destroy
148
+ Bustle::Subscribers.get(subscriber).destroy
149
+ Bustle::Publishers.get(publisher).destroy
150
+ Bustle::Subscriptions.get(bustle_publisher, bustle_subscriber).destroy
148
151
  ```
149
152
 
150
153
  #### Publish an Activity
@@ -158,7 +161,7 @@ Bustle::Publisher.publish action, activity
158
161
  post = Post.find(1)
159
162
  comment = post.comments.add(:content => "I'm a comment")
160
163
  Bustle::Publishers.add post
161
- publisher = Bustle::Publishers.find post
164
+ publisher = Bustle::Publishers.get post
162
165
  publisher.publish 'new', comment
163
166
  ```
164
167
 
@@ -172,7 +175,7 @@ Bustle::Activities.for bustle_subscriber
172
175
  Bustle::Subscriber.activities
173
176
 
174
177
  # example
175
- subscriber = Bustle::Subscribers.find(User.first)
178
+ subscriber = Bustle::Subscribers.get(User.first)
176
179
  subscriber.activities
177
180
  ```
178
181
 
@@ -184,27 +187,21 @@ Bustle::Activities.by bustle_publisher
184
187
  Bustle::Publisher.activities
185
188
 
186
189
  # example
187
- publisher = Bustle::Publishers.find(Post.first)
190
+ publisher = Bustle::Publishers.get(Post.first)
188
191
  publisher.activities
189
192
  ```
190
193
 
191
- ##### Activities Filtering
194
+ #### Filtering (for Activities and Subscriptions)
192
195
 
193
196
  ```ruby
194
- Bustle::Activities.for(bustle_subscriber).filter :key => :value
197
+ Bustle::Activities.filter :key => :value
198
+ Bustle::Subscriptions.filter :key => :value
195
199
  # or
196
- Bustle::Subscriber.activities.filter :key => :value
200
+ Bustle::Subscriber.activities :key => :value
197
201
 
198
202
  # example
199
- subscriber = Bustle::Subscribers.find(User.first)
200
- subscriber.activities.filter :action => 'new'
201
- subscriber.activities.by(publisher).filter(:action => 'new')
202
- ```
203
-
204
- Activities are normal enumerable objects from your chosen storage, so in ActiveRecord's case, you may use any Arel methods to query the result:
205
-
206
- ```ruby
207
- subscriber.activities.filter(:action => 'new').order('created_at ASC').limit(10)
203
+ subscriber = Bustle::Subscribers.get(User.first)
204
+ subscriber.activities :action => 'new'
208
205
  ```
209
206
 
210
207
  ## License
data/bustle.gemspec CHANGED
@@ -15,7 +15,15 @@ Gem::Specification.new do |gem|
15
15
  gem.require_paths = ["lib"]
16
16
  gem.version = Bustle::VERSION
17
17
 
18
+ gem.add_dependency 'activesupport', '>= 3.0'
19
+ gem.add_dependency 'orm_adapter', '~> 0.1.0'
20
+
18
21
  gem.add_development_dependency 'rake'
19
22
  gem.add_development_dependency 'simplecov'
20
- gem.add_development_dependency 'minitest-colorize'
23
+ gem.add_development_dependency 'rspec'
24
+ gem.add_development_dependency 'factory_girl'
25
+ gem.add_development_dependency 'database_cleaner'
26
+ gem.add_development_dependency 'pry'
27
+ gem.add_development_dependency 'activerecord', '>= 3.0'
28
+ gem.add_development_dependency 'sqlite3'
21
29
  end
@@ -0,0 +1,25 @@
1
+ module Bustle
2
+ class Activities
3
+ RESOURCE_NAME = 'Bustle::Activity'
4
+
5
+ include Concern::Filterable
6
+ include Concern::ByPublisher
7
+
8
+ class << self
9
+ def add(publisher, action, activity_resource)
10
+ Activity.to_adapter.create!(
11
+ :resource_class => activity_resource.class.name,
12
+ :resource_id => activity_resource.id,
13
+ :action => action,
14
+ :publisher_id => publisher.id
15
+ )
16
+ end
17
+
18
+ def for(subscriber, conditions = {})
19
+ publisher_ids = Subscriptions.for(subscriber).map(&:publisher_id)
20
+
21
+ filter({ :publisher_id => publisher_ids }.merge(conditions))
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,11 @@
1
+ module Bustle::Concern
2
+ module ByPublisher
3
+ extend ActiveSupport::Concern
4
+
5
+ module ClassMethods
6
+ def by(publisher, conditions = {})
7
+ filter({ :publisher_id => publisher.id }.merge(conditions))
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ module Bustle::Concern
2
+ module Filterable
3
+ extend ActiveSupport::Concern
4
+
5
+ module ClassMethods
6
+ def filter(options = {})
7
+ self::RESOURCE_NAME.constantize.to_adapter.find_all(options)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ module Bustle::Concern
2
+ module ForSubscriber
3
+ extend ActiveSupport::Concern
4
+
5
+ module ClassMethods
6
+ def for(subscriber, conditions = {})
7
+ filter({ :subscriber_id => subscriber.id }.merge(conditions))
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,25 @@
1
+ module Bustle::Concern
2
+ module ResourceCollection
3
+ extend ActiveSupport::Concern
4
+
5
+ module ClassMethods
6
+ def add(resource)
7
+ self::RESOURCE_NAME.constantize.to_adapter.create!(
8
+ :resource_class => resource.class.name,
9
+ :resource_id => resource.id
10
+ )
11
+ end
12
+
13
+ def get(resource)
14
+ self::RESOURCE_NAME.constantize.to_adapter.find_first(
15
+ :resource_class => resource.class.name,
16
+ :resource_id => resource.id
17
+ )
18
+ end
19
+
20
+ def remove(resource)
21
+ get(resource).destroy
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,4 @@
1
+ module Bustle
2
+ class Activity < "#{Bustle.storage}::Activity".constantize
3
+ end
4
+ end
@@ -0,0 +1,11 @@
1
+ module Bustle
2
+ class Publisher < "#{Bustle.storage}::Publisher".constantize
3
+ def publish(action, activity_resource)
4
+ Activities.add self, action, activity_resource
5
+ end
6
+
7
+ def activities(conditions = {})
8
+ Activities.by(self, conditions)
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,7 @@
1
+ module Bustle
2
+ class Subscriber < "#{Bustle.storage}::Subscriber".constantize
3
+ def activities(conditions = {})
4
+ Activities.for(self, conditions)
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,4 @@
1
+ module Bustle
2
+ class Subscription < "#{Bustle.storage}::Subscription".constantize
3
+ end
4
+ end
@@ -0,0 +1,7 @@
1
+ module Bustle
2
+ class Publishers
3
+ RESOURCE_NAME = 'Bustle::Publisher'
4
+
5
+ include Concern::ResourceCollection
6
+ end
7
+ end
@@ -0,0 +1,5 @@
1
+ module Bustle::Storage::ActiveRecord
2
+ class Activity < ::ActiveRecord::Base
3
+ self.table_name = 'bustle_activities'
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ module Bustle::Storage::ActiveRecord
2
+ class Publisher < ::ActiveRecord::Base
3
+ self.table_name = 'bustle_publishers'
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ module Bustle::Storage::ActiveRecord
2
+ class Subscriber < ::ActiveRecord::Base
3
+ self.table_name = 'bustle_subscribers'
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ module Bustle::Storage::ActiveRecord
2
+ class Subscription < ::ActiveRecord::Base
3
+ self.table_name = 'bustle_subscriptions'
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ require 'active_record'
2
+ require 'bustle/storage/active_record/activity'
3
+ require 'bustle/storage/active_record/publisher'
4
+ require 'bustle/storage/active_record/subscriber'
5
+ require 'bustle/storage/active_record/subscription'
@@ -0,0 +1,7 @@
1
+ module Bustle
2
+ class Subscribers
3
+ RESOURCE_NAME = 'Bustle::Subscriber'
4
+
5
+ include Concern::ResourceCollection
6
+ end
7
+ end
@@ -0,0 +1,29 @@
1
+ module Bustle
2
+ class Subscriptions
3
+ RESOURCE_NAME = 'Bustle::Subscription'
4
+
5
+ include Concern::Filterable
6
+ include Concern::ByPublisher
7
+ include Concern::ForSubscriber
8
+
9
+ class << self
10
+ def add(publisher, subscriber)
11
+ Subscription.to_adapter.create!(
12
+ :publisher_id => publisher.id,
13
+ :subscriber_id => subscriber.id
14
+ )
15
+ end
16
+
17
+ def get(publisher, subscriber)
18
+ Subscription.to_adapter.find_first(
19
+ :publisher_id => publisher.id,
20
+ :subscriber_id => subscriber.id
21
+ )
22
+ end
23
+
24
+ def remove(publisher, subscriber)
25
+ get(publisher, subscriber).destroy
26
+ end
27
+ end
28
+ end
29
+ end
@@ -1,3 +1,3 @@
1
1
  module Bustle
2
- VERSION = "0.0.1"
2
+ VERSION = "0.1.0"
3
3
  end
data/lib/bustle.rb CHANGED
@@ -1 +1,31 @@
1
+ require 'active_support/dependencies'
2
+ require 'active_support/concern'
3
+
1
4
  require "bustle/version"
5
+ require "bustle/concerns/filterable"
6
+ require "bustle/concerns/by_publisher"
7
+ require "bustle/concerns/for_subscriber"
8
+ require "bustle/concerns/resource_collection"
9
+ require "bustle/activities"
10
+ require "bustle/publishers"
11
+ require "bustle/subscribers"
12
+ require "bustle/subscriptions"
13
+
14
+ module Bustle
15
+ mattr_accessor :storage
16
+
17
+ module Storage
18
+ autoload :ActiveRecord, 'bustle/storage/active_record'
19
+ end
20
+
21
+ def self.config
22
+ yield self
23
+
24
+ require 'bustle/models/activity'
25
+ require 'bustle/models/publisher'
26
+ require 'bustle/models/subscriber'
27
+ require 'bustle/models/subscription'
28
+
29
+ require 'orm_adapter'
30
+ end
31
+ end
@@ -0,0 +1,65 @@
1
+ require_relative '../spec_helper'
2
+
3
+ module Bustle
4
+ describe Activities do
5
+ include_context 'app_resources'
6
+
7
+ let(:publisher) { Publishers.add post }
8
+ let(:publisher2) { Publishers.add user }
9
+ let(:subscriber) { Subscribers.add post }
10
+ let(:subscriber2) { Subscribers.add user }
11
+
12
+ it "creates an activity" do
13
+ Activities.add publisher, 'show', comment
14
+
15
+ activity = Activity.to_adapter.get(1)
16
+ activity.resource_class.should == 'Bustle::Dummy::Comment'
17
+ activity.resource_id.should == 101
18
+ activity.action.should == 'show'
19
+ activity.publisher_id.should == publisher.id
20
+ end
21
+
22
+ context "multiple activities" do
23
+ before do
24
+ 2.times { Activities.add publisher, 'show', comment }
25
+ 1.times { Activities.add publisher, 'reply', comment }
26
+ 2.times { Activities.add publisher2, 'reply', comment }
27
+ end
28
+
29
+ it "finds all activities" do
30
+ Activities.filter.count.should == 5
31
+ end
32
+
33
+ context "#by" do
34
+ it "finds all activities by a publisher" do
35
+ Activities.by(publisher).count.should == 3
36
+ end
37
+
38
+ it "finds all activities filtered by conditions" do
39
+ Activities.by(publisher, {
40
+ :action => 'show'
41
+ }).count.should == 2
42
+ end
43
+ end
44
+
45
+ context "#for" do
46
+ before do
47
+ Subscriptions.add publisher, subscriber
48
+ Subscriptions.add publisher, subscriber2
49
+ Subscriptions.add publisher2, subscriber2
50
+ end
51
+
52
+ it "finds all activities for a subscriber" do
53
+ Activities.for(subscriber).count.should == 3
54
+ Activities.for(subscriber2).count.should == 5
55
+ end
56
+
57
+ it "finds all activities filtered by conditions" do
58
+ Activities.for(subscriber2, {
59
+ :publisher_id => publisher2.id
60
+ }).count.should == 2
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,9 @@
1
+ require_relative '../../spec_helper'
2
+
3
+ module Bustle
4
+ describe 'Activity' do
5
+ let(:described_class) { Activity }
6
+
7
+ it_behaves_like 'model'
8
+ end
9
+ end
@@ -0,0 +1,17 @@
1
+ require_relative '../../spec_helper'
2
+
3
+ module Bustle
4
+ describe 'Publisher' do
5
+ let(:described_class) { Publisher }
6
+
7
+ include_context 'app_resources'
8
+ it_behaves_like 'model'
9
+ it_behaves_like 'has_activities'
10
+
11
+ it "is able to publish an activity" do
12
+ publisher = Publishers.add post
13
+ publisher.publish 'reply', comment
14
+ Activity.to_adapter.get(1).action.should == 'reply'
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,11 @@
1
+ require_relative '../../spec_helper'
2
+
3
+ module Bustle
4
+ describe 'Subscriber' do
5
+ let(:described_class) { Subscriber }
6
+
7
+ include_context 'app_resources'
8
+ it_behaves_like 'model'
9
+ it_behaves_like 'has_activities'
10
+ end
11
+ end
@@ -0,0 +1,9 @@
1
+ require_relative '../../spec_helper'
2
+
3
+ module Bustle
4
+ describe 'Subscription' do
5
+ let(:described_class) { Subscription }
6
+
7
+ it_behaves_like 'model'
8
+ end
9
+ end
@@ -0,0 +1,8 @@
1
+ require_relative '../spec_helper'
2
+
3
+ module Bustle
4
+ describe Publishers do
5
+ include_context 'app_resources'
6
+ it_behaves_like 'resource_collection'
7
+ end
8
+ end
@@ -0,0 +1,23 @@
1
+ require_relative '../../spec_helper'
2
+
3
+ module Bustle::Storage
4
+ describe ActiveRecord do
5
+ before(:all) do
6
+ Bustle.config { |c| c.storage = ActiveRecord }
7
+ end
8
+
9
+ it "loads storage model files" do
10
+ ActiveRecord.const_defined?(:Activity).should == true
11
+ ActiveRecord.const_defined?(:Publisher).should == true
12
+ ActiveRecord.const_defined?(:Subscriber).should == true
13
+ ActiveRecord.const_defined?(:Subscription).should == true
14
+ end
15
+
16
+ it "has correct table names" do
17
+ ActiveRecord::Activity.table_name.should == 'bustle_activities'
18
+ ActiveRecord::Publisher.table_name.should == 'bustle_publishers'
19
+ ActiveRecord::Subscriber.table_name.should == 'bustle_subscribers'
20
+ ActiveRecord::Subscription.table_name.should == 'bustle_subscriptions'
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,8 @@
1
+ require_relative '../spec_helper'
2
+
3
+ module Bustle
4
+ describe Subscribers do
5
+ include_context 'app_resources'
6
+ it_behaves_like 'resource_collection'
7
+ end
8
+ end
@@ -0,0 +1,76 @@
1
+ require_relative '../spec_helper'
2
+
3
+ module Bustle
4
+ describe Subscriptions do
5
+ include_context 'app_resources'
6
+
7
+ let(:publisher) { Publishers.add post }
8
+ let(:publisher2) { Publishers.add Dummy::Post.create!(:title => '') }
9
+ let(:subscriber) { Subscribers.add user }
10
+ let(:subscriber2) { Subscribers.add Dummy::User.create!(:name => '') }
11
+
12
+ before do
13
+ Subscriptions.add publisher, subscriber
14
+ end
15
+
16
+ it "creates a subscription" do
17
+ subscription = Subscription.to_adapter.get(1)
18
+
19
+ subscription.publisher_id.should == publisher.id
20
+ subscription.subscriber_id.should == subscriber.id
21
+ end
22
+
23
+ it "finds a subscription" do
24
+ subscription = Subscriptions.get publisher, subscriber
25
+
26
+ subscription.publisher_id.should == publisher.id
27
+ subscription.subscriber_id.should == subscriber.id
28
+ end
29
+
30
+ it "removes a subscription" do
31
+ subscription = Subscriptions.get publisher, subscriber
32
+
33
+ Subscriptions.remove publisher, subscriber
34
+ Subscriptions.filter.count.should == 0
35
+ end
36
+
37
+ context "finding multiple subscriptions" do
38
+ before do
39
+ Subscriptions.add publisher2, subscriber
40
+ Subscriptions.add publisher, subscriber2
41
+ Subscriptions.add publisher2, subscriber2
42
+ end
43
+
44
+ it "finds all subscriptions" do
45
+ Subscriptions.filter.count.should == 4
46
+ end
47
+
48
+ it "finds specific subscriptions" do
49
+ Subscriptions.filter({
50
+ :publisher_id => publisher.id,
51
+ :subscriber_id => subscriber.id
52
+ }).count.should == 1
53
+ end
54
+
55
+ it "finds subscriptions by a publisher" do
56
+ Subscriptions.by(publisher).count.should == 2
57
+ end
58
+
59
+ it "finds subscriptions by a publisher and conditions" do
60
+ Subscriptions.by(publisher, {
61
+ :subscriber_id => subscriber2.id
62
+ }).count.should == 1
63
+ end
64
+
65
+ it "finds subscriptions for a subscriber" do
66
+ Subscriptions.for(subscriber).count.should == 2
67
+ end
68
+
69
+ it "finds subscriptions for a subscriber and conditions" do
70
+ Subscriptions.for(subscriber, {
71
+ :publisher_id => publisher.id
72
+ }).count.should == 1
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,12 @@
1
+ require_relative 'spec_helper'
2
+
3
+ describe Bustle do
4
+ context "configuration" do
5
+ [:storage].each do |option|
6
+ it "sets the config option #{option}" do
7
+ Bustle.config { |c| c.send "#{option}=", 'dummy' }
8
+ Bustle.send(option).should == 'dummy'
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,35 @@
1
+ require 'simplecov'
2
+ SimpleCov.start
3
+
4
+ require 'rspec/autorun'
5
+ require 'factory_girl'
6
+ require 'database_cleaner'
7
+ require 'pry'
8
+
9
+ require File.expand_path('../../lib/bustle', __FILE__)
10
+ Dir[File.expand_path('../../specs/support/**/*.rb', __FILE__)].each {|f| require f}
11
+
12
+ BUSTLE_STORAGES = [
13
+ Bustle::Storage::ActiveRecord
14
+ ]
15
+
16
+ RSpec.configure do |config|
17
+ config.include FactoryGirl::Syntax::Methods
18
+
19
+ config.before(:suite) do
20
+ module Bustle::Dummy
21
+ class User < ActiveRecord::Base; end
22
+ class Post < ActiveRecord::Base; end
23
+ class Comment < ActiveRecord::Base; end
24
+ end
25
+
26
+ Bustle.config do |c|
27
+ c.storage = Bustle::Storage::ActiveRecord
28
+ end
29
+ end
30
+
31
+ DatabaseCleaner.strategy = :truncation
32
+ config.before do
33
+ DatabaseCleaner.clean
34
+ end
35
+ end
@@ -0,0 +1,45 @@
1
+ require 'active_record'
2
+
3
+ ActiveRecord::Base.establish_connection(:adapter => 'sqlite3', :database => ":memory:")
4
+
5
+ ActiveRecord::Migration.suppress_messages do
6
+ ActiveRecord::Schema.define(:version => 0) do
7
+ create_table :users, :force => true do |t|
8
+ t.string :name
9
+ end
10
+
11
+ create_table :posts, :force => true do |t|
12
+ t.string :title
13
+ end
14
+
15
+ create_table :comments, :force => true do |t|
16
+ t.string :title
17
+ end
18
+
19
+ create_table :bustle_activities, :force => true do |t|
20
+ t.string :resource_class
21
+ t.integer :resource_id
22
+ t.string :action
23
+ t.integer :publisher_id
24
+ t.timestamps
25
+ end
26
+
27
+ create_table :bustle_publishers, :force => true do |t|
28
+ t.string :resource_class
29
+ t.integer :resource_id
30
+ t.timestamps
31
+ end
32
+
33
+ create_table :bustle_subscribers, :force => true do |t|
34
+ t.string :resource_class
35
+ t.integer :resource_id
36
+ t.timestamps
37
+ end
38
+
39
+ create_table :bustle_subscriptions, :force => true do |t|
40
+ t.integer :publisher_id
41
+ t.integer :subscriber_id
42
+ t.timestamps
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,11 @@
1
+ shared_context 'app_resources' do
2
+ let(:user) { Bustle::Dummy::User.create! :name => 'Fred' }
3
+ let(:post) { Bustle::Dummy::Post.create! :title => 'hello' }
4
+ let(:comment) { Bustle::Dummy::Comment.create! :title => 'hi' }
5
+
6
+ before do
7
+ user.update_attribute :id, 42
8
+ post.update_attribute :id, 69
9
+ comment.update_attribute :id, 101
10
+ end
11
+ end
@@ -0,0 +1,17 @@
1
+ shared_examples 'has_activities' do
2
+ context "activities" do
3
+ let(:publisher) { Bustle::Publishers.add post }
4
+ let(:subscriber) { Bustle::Subscribers.add user }
5
+
6
+ before do
7
+ Bustle::Subscriptions.add publisher, subscriber
8
+ end
9
+
10
+ it "has activities" do
11
+ 2.times { publisher.publish 'show', comment }
12
+
13
+ publisher.activities.count.should == 2
14
+ subscriber.activities.count.should == 2
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,38 @@
1
+ shared_examples 'model' do
2
+ BUSTLE_STORAGES.each do |storage|
3
+ let(:factory_class) { described_class }
4
+ let(:factory_name) { factory_class.to_s.split('::').last.downcase.to_sym }
5
+
6
+ before(:all) do
7
+ Bustle.config { |c| c.storage = storage }
8
+
9
+ _factory_class = described_class
10
+ _factory_name = factory_name
11
+
12
+ FactoryGirl.define do
13
+ factory _factory_name, :class => _factory_class do
14
+ end
15
+ end
16
+ end
17
+
18
+ context "storage #{storage}" do
19
+ it "has correct parent classes" do
20
+ described_class.superclass.to_s.should == "#{Bustle.storage}::#{described_class.name.split('::').last}"
21
+ end
22
+
23
+ it "is able to create a new instance" do
24
+ described_class.to_adapter.respond_to?(:create!).should == true
25
+ end
26
+
27
+ it "is able to destroy itself" do
28
+ described_class.new.respond_to?(:destroy).should == true
29
+
30
+ create factory_name
31
+ described_class.to_adapter.find_all({}).count.should == 1
32
+
33
+ described_class.to_adapter.find_first({}).destroy
34
+ described_class.to_adapter.find_all({}).count.should == 0
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,24 @@
1
+ shared_examples 'resource_collection' do
2
+ let(:resource_class) { described_class::RESOURCE_NAME.constantize.to_adapter }
3
+
4
+ before do
5
+ described_class.add user
6
+ end
7
+
8
+ it "adds a model record" do
9
+ model = resource_class.get(1)
10
+ model.resource_class.should == 'Bustle::Dummy::User'
11
+ model.resource_id.should == 42
12
+ end
13
+
14
+ it "finds a model record" do
15
+ model = described_class.get user
16
+ model.resource_class.should == 'Bustle::Dummy::User'
17
+ model.resource_id.should == 42
18
+ end
19
+
20
+ it "removes a model record" do
21
+ described_class.remove(user)
22
+ resource_class.find_all({}).count.should == 0
23
+ end
24
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bustle
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.1.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,8 +9,40 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-06-10 00:00:00.000000000 Z
12
+ date: 2012-06-13 00:00:00.000000000 Z
13
13
  dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: activesupport
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: orm_adapter
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: 0.1.0
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: 0.1.0
14
46
  - !ruby/object:Gem::Dependency
15
47
  name: rake
16
48
  requirement: !ruby/object:Gem::Requirement
@@ -44,7 +76,87 @@ dependencies:
44
76
  - !ruby/object:Gem::Version
45
77
  version: '0'
46
78
  - !ruby/object:Gem::Dependency
47
- name: minitest-colorize
79
+ name: rspec
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: factory_girl
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
+ - !ruby/object:Gem::Dependency
111
+ name: database_cleaner
112
+ requirement: !ruby/object:Gem::Requirement
113
+ none: false
114
+ requirements:
115
+ - - ! '>='
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ none: false
122
+ requirements:
123
+ - - ! '>='
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
126
+ - !ruby/object:Gem::Dependency
127
+ name: pry
128
+ requirement: !ruby/object:Gem::Requirement
129
+ none: false
130
+ requirements:
131
+ - - ! '>='
132
+ - !ruby/object:Gem::Version
133
+ version: '0'
134
+ type: :development
135
+ prerelease: false
136
+ version_requirements: !ruby/object:Gem::Requirement
137
+ none: false
138
+ requirements:
139
+ - - ! '>='
140
+ - !ruby/object:Gem::Version
141
+ version: '0'
142
+ - !ruby/object:Gem::Dependency
143
+ name: activerecord
144
+ requirement: !ruby/object:Gem::Requirement
145
+ none: false
146
+ requirements:
147
+ - - ! '>='
148
+ - !ruby/object:Gem::Version
149
+ version: '3.0'
150
+ type: :development
151
+ prerelease: false
152
+ version_requirements: !ruby/object:Gem::Requirement
153
+ none: false
154
+ requirements:
155
+ - - ! '>='
156
+ - !ruby/object:Gem::Version
157
+ version: '3.0'
158
+ - !ruby/object:Gem::Dependency
159
+ name: sqlite3
48
160
  requirement: !ruby/object:Gem::Requirement
49
161
  none: false
50
162
  requirements:
@@ -67,13 +179,47 @@ extensions: []
67
179
  extra_rdoc_files: []
68
180
  files:
69
181
  - .gitignore
182
+ - .rspec
183
+ - .travis.yml
70
184
  - Gemfile
71
185
  - README.md
72
186
  - Rakefile
73
187
  - bustle.gemspec
74
188
  - lib/bustle.rb
189
+ - lib/bustle/activities.rb
190
+ - lib/bustle/concerns/by_publisher.rb
191
+ - lib/bustle/concerns/filterable.rb
192
+ - lib/bustle/concerns/for_subscriber.rb
193
+ - lib/bustle/concerns/resource_collection.rb
194
+ - lib/bustle/models/activity.rb
195
+ - lib/bustle/models/publisher.rb
196
+ - lib/bustle/models/subscriber.rb
197
+ - lib/bustle/models/subscription.rb
198
+ - lib/bustle/publishers.rb
199
+ - lib/bustle/storage/active_record.rb
200
+ - lib/bustle/storage/active_record/activity.rb
201
+ - lib/bustle/storage/active_record/publisher.rb
202
+ - lib/bustle/storage/active_record/subscriber.rb
203
+ - lib/bustle/storage/active_record/subscription.rb
204
+ - lib/bustle/subscribers.rb
205
+ - lib/bustle/subscriptions.rb
75
206
  - lib/bustle/version.rb
76
- - lib/specs/spec_helper.rb
207
+ - specs/bustle/activities_spec.rb
208
+ - specs/bustle/models/activity_spec.rb
209
+ - specs/bustle/models/publisher_spec.rb
210
+ - specs/bustle/models/subscriber_spec.rb
211
+ - specs/bustle/models/subscription_spec.rb
212
+ - specs/bustle/publishers_spec.rb
213
+ - specs/bustle/storage/active_record_spec.rb
214
+ - specs/bustle/subscribers_spec.rb
215
+ - specs/bustle/subscriptions_spec.rb
216
+ - specs/bustle_spec.rb
217
+ - specs/spec_helper.rb
218
+ - specs/support/active_record_tables.rb
219
+ - specs/support/shared_context/app_resources.rb
220
+ - specs/support/shared_examples/has_activities.rb
221
+ - specs/support/shared_examples/model.rb
222
+ - specs/support/shared_examples/resource_collection.rb
77
223
  homepage: https://github.com/fredwu/bustle
78
224
  licenses: []
79
225
  post_install_message:
@@ -88,7 +234,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
88
234
  version: '0'
89
235
  segments:
90
236
  - 0
91
- hash: 3392583862411400704
237
+ hash: 1631565603439208655
92
238
  required_rubygems_version: !ruby/object:Gem::Requirement
93
239
  none: false
94
240
  requirements:
@@ -97,7 +243,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
97
243
  version: '0'
98
244
  segments:
99
245
  - 0
100
- hash: 3392583862411400704
246
+ hash: 1631565603439208655
101
247
  requirements: []
102
248
  rubyforge_project:
103
249
  rubygems_version: 1.8.24
@@ -1,8 +0,0 @@
1
- require 'simplecov'
2
- SimpleCov.start
3
-
4
- require 'minitest-colorize'
5
- require 'minitest/autorun'
6
- require 'minitest/spec'
7
-
8
- require File.expand_path('../../lib/bustle', __FILE__)