social_connections 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,5 @@
1
+ .bundle
2
+ *.sqlite3
3
+ *.log
4
+ tmp/
5
+ .*.swp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in social_connections.gemspec
4
+ gemspec
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 [name of plugin creator]
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 ADDED
@@ -0,0 +1,25 @@
1
+ SocialConnections
2
+ =================
3
+
4
+ Introduction goes here.
5
+
6
+
7
+ Example
8
+ =======
9
+
10
+ Example goes here.
11
+
12
+
13
+ How to run the Tests
14
+ ====================
15
+
16
+ Run test in ./test:
17
+
18
+ rake
19
+
20
+ Run specs in ./spec:
21
+
22
+ rspec spec
23
+
24
+
25
+ Copyright (c) 2011 [name of plugin creator], released under the MIT license
data/Rakefile ADDED
@@ -0,0 +1,26 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rake/rdoctask'
4
+ require 'bundler'
5
+
6
+ Bundler::GemHelper.install_tasks
7
+
8
+ desc 'Default: run unit tests.'
9
+ task :default => :test
10
+
11
+ desc 'Test the social_connections plugin.'
12
+ Rake::TestTask.new(:test) do |t|
13
+ t.libs << 'lib'
14
+ t.libs << 'test'
15
+ t.pattern = 'test/**/*_test.rb'
16
+ t.verbose = true
17
+ end
18
+
19
+ desc 'Generate documentation for the social_connections plugin.'
20
+ Rake::RDocTask.new(:rdoc) do |rdoc|
21
+ rdoc.rdoc_dir = 'rdoc'
22
+ rdoc.title = 'SocialConnections'
23
+ rdoc.options << '--line-numbers' << '--inline-source'
24
+ rdoc.rdoc_files.include('README')
25
+ rdoc.rdoc_files.include('lib/**/*.rb')
26
+ end
@@ -0,0 +1 @@
1
+ keep this dir so that tests work.
data/init.rb ADDED
@@ -0,0 +1,3 @@
1
+ # Include hook code here
2
+
3
+ require 'social_connections'
data/install.rb ADDED
@@ -0,0 +1 @@
1
+ # Install hook code here
@@ -0,0 +1,17 @@
1
+ class DigestMailer < ActionMailer::Base
2
+
3
+ default :from => 'from@example.com'
4
+
5
+ append_view_path(File.dirname(__FILE__) + '../../views')
6
+
7
+ def digest(feed, email)
8
+
9
+ @feed = feed
10
+ @email = email
11
+ mail(:to => @email, :subject => 'Digest') do |format|
12
+ format.html
13
+ end
14
+
15
+ end
16
+
17
+ end
@@ -0,0 +1,37 @@
1
+ class SocialActivity < ActiveRecord::Base
2
+
3
+ belongs_to :owner, :polymorphic => true
4
+ belongs_to :subject, :polymorphic => true
5
+ belongs_to :target, :polymorphic => true
6
+
7
+ def options
8
+ JSON.parse options_as_json
9
+ end
10
+
11
+ def self.create_activities(subject, verb, object, options = {})
12
+ # TODO: we may want to delay this
13
+ activities = []
14
+ [ subject, object ].each do |owner| # one activity for the subject, and one for the object
15
+ activities << SocialActivity.create(:owner => owner,
16
+ :subject => subject,
17
+ :verb => verb,
18
+ :target => object,
19
+ :options_as_json => options.to_json)
20
+ end
21
+ [ subject, object ].each do |who| # for both subject and object...
22
+ who.incoming_social_connections.each do |connection|
23
+ activities << SocialActivity.create(:owner => connection.source, # ... an activity for each one connected
24
+ :subject => subject,
25
+ :verb => verb,
26
+ :target => object,
27
+ :options_as_json => options.to_json)
28
+ end
29
+ end
30
+ activities
31
+ end
32
+
33
+ def self.unseen_activities(for_whom)
34
+ SocialActivity.where('owner_id = ? and owner_type = ? and unseen = ?', for_whom.id, for_whom.class.name, true)
35
+ end
36
+
37
+ end
@@ -0,0 +1,6 @@
1
+ class SocialConnection < ActiveRecord::Base
2
+
3
+ belongs_to :source, :polymorphic => true
4
+ belongs_to :target, :polymorphic => true
5
+
6
+ end
@@ -0,0 +1 @@
1
+ <p>Hi, <%= @email %></p>
@@ -0,0 +1,13 @@
1
+ class CreateSocialConnections < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :social_connections do |t|
4
+ t.integer :source_id
5
+ t.string :source_type
6
+ t.integer :target_id
7
+ t.string :target_type
8
+ end
9
+ end
10
+ def self.down
11
+ drop_table :social_connections
12
+ end
13
+ end
@@ -0,0 +1,58 @@
1
+ module SocialConnections
2
+
3
+ def self.included(base)
4
+ base.send :extend, ClassMethods
5
+ end
6
+
7
+ module ClassMethods
8
+ def acts_as_connectable(options = {})
9
+ cattr_accessor :acts_as_connectable_options
10
+ send :acts_as_connectable_options=, options
11
+ send :include, InstanceMethods
12
+ end
13
+ end
14
+
15
+ module InstanceMethods
16
+
17
+ def outgoing_social_connections
18
+ SocialConnection.where("source_id = ? and source_type = ?", self.id, self.class.name)
19
+ end
20
+
21
+ def incoming_social_connections
22
+ SocialConnection.where("target_id = ? and target_type = ?", self.id, self.class.name)
23
+ end
24
+
25
+ def connect_to(other)
26
+ raise ArgumentError.new("other cannot be nil") if other.nil?
27
+ c = SocialConnection.new
28
+ c.target = other
29
+ c.source = self
30
+ c
31
+ end
32
+
33
+ def acts_as_connectable_verbs
34
+ acts_as_connectable_options[:verbs] || [ :likes ]
35
+ end
36
+
37
+ def method_missing(name, *args)
38
+ if acts_as_connectable_verbs.include? name
39
+ verb = name
40
+ object = args[0]
41
+ options = args[1] || {}
42
+ create_activity(verb, object, options)
43
+ else
44
+ super
45
+ end
46
+ end
47
+
48
+ private
49
+
50
+ def create_activity(verb, object, options = {})
51
+ SocialActivity.create_activities(self, verb, object, options)
52
+ end
53
+
54
+ end
55
+
56
+ end
57
+
58
+ ActiveRecord::Base.send :include, SocialConnections
@@ -0,0 +1,14 @@
1
+ module SocialConnections
2
+
3
+ class Feed
4
+ attr_accessor :activities
5
+ def initialize(activities)
6
+ self.activities = activities
7
+ end
8
+ end
9
+
10
+ def self.aggregate(who)
11
+ Feed.new(SocialActivity.unseen_activities(who))
12
+ end
13
+
14
+ end
@@ -0,0 +1,3 @@
1
+ module SocialConnections
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,12 @@
1
+ # SocialConnections
2
+
3
+ require 'social_connections/acts_as_connectable'
4
+ require 'social_connections/social_aggregator'
5
+
6
+ %w{ models mailers }.each do |dir|
7
+ path = File.join(File.dirname(__FILE__), 'app', dir)
8
+ $LOAD_PATH << path
9
+ ActiveSupport::Dependencies.autoload_paths << path
10
+ ActiveSupport::Dependencies.autoload_once_paths.delete(path)
11
+ end
12
+
@@ -0,0 +1,14 @@
1
+ namespace :db do
2
+ namespace :migrate do
3
+ description = "migrate the db in vendor/plugins/social_connections/lib/db/migrate"
4
+
5
+ puts "migration?"
6
+
7
+ desc description
8
+ task :social_connections => :environment do
9
+ ActiveRecord::Migration.verbose = true
10
+ ActiveRecord::Migrator.migrate("vendor/plugins/social_connections/lib/db/migrate/", ENV['VERSION'] ? ENV['VERSION'].to_i : nil)
11
+ Rake::Task["db:schema:dump"].invoke if ActiveRecord::Base.schema_format == :ruby
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,22 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "social_connections/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "social_connections"
7
+ s.version = SocialConnections::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Chris"]
10
+ s.email = ["christian.oloff+gem@gmail.com"]
11
+ s.homepage = "https://sites.google.com/site/socialconnections7/"
12
+ s.summary = %q{Social connections for ActiveRecord.}
13
+ s.description = %q{The idea is to provide pluggable social connections, activities and a method to digest those activities (e.g. in daily emails).}
14
+
15
+ s.rubyforge_project = "social_connections"
16
+
17
+ s.files = `git ls-files`.split("\n")
18
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
19
+ # s.test_files.reject! { |fn| fn.include? ".swp" }
20
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
21
+ s.require_paths = ["lib"]
22
+ end
@@ -0,0 +1,41 @@
1
+ require File.dirname(__FILE__) + '/../test/test_helper'
2
+
3
+ describe "Aggregation" do
4
+
5
+ before(:each) do
6
+ load_schema
7
+ @tim = Connectable.create(:name => 'Tim', :email => 'tim@test.com')
8
+ @tom = Connectable.create(:name => 'Tom', :email => 'tom@test.com')
9
+ end
10
+
11
+ it "aggregates activities into feeds for specific users" do
12
+ @tim.likes(@tom)
13
+ @tim.comments(@tom, :comment => "This is cool")
14
+ feed_tim = SocialConnections.aggregate(@tim)
15
+ feed_tim.activities.count.should be_>(1)
16
+ feed_tim.activities.each {|a| puts "For Tim: #{a.subject} #{a.verb} #{a.target}, options=#{a.options}"}
17
+ feed_tom = SocialConnections.aggregate(@tom)
18
+ feed_tom.activities.each {|a| puts "For Tom: #{a.subject} #{a.verb} #{a.target}, options=#{a.options}"}
19
+ feed_tom.activities.count.should be_>(1)
20
+ end
21
+
22
+ describe DigestMailer do
23
+
24
+ before(:each) do
25
+ @tim.likes(@tom)
26
+ @tim.comments(@tom, :comment => "This is boring...")
27
+ @feed_tim = SocialConnections.aggregate(@tim)
28
+ @mail = DigestMailer.digest(@feed_tim, @tim.email)
29
+ end
30
+
31
+ it "delivers an email which references all unseen activities ..." do
32
+ @mail.subject.should eq("Digest")
33
+ @mail.to.should eq(["tim@test.com"])
34
+ @mail.from.should eq(["from@example.com"])
35
+ @mail.body.encoded.should match("Hi")
36
+ pending
37
+ end
38
+
39
+ end
40
+
41
+ end
@@ -0,0 +1,67 @@
1
+ require File.dirname(__FILE__) + '/../test/test_helper'
2
+
3
+ describe "Subjects" do
4
+
5
+ before(:each) do
6
+ load_schema
7
+ @sub = Connectable.create(:name => 'Tim')
8
+ @obj = Connectable.create(:name => 'Tom')
9
+ end
10
+
11
+ it "can emit activities" do
12
+ activities = @sub.likes(@obj) # what about the 'owner'? everybody 'connected' to the subject *and*
13
+ # the object becomes an owner!
14
+ activities.should_not be_nil
15
+ activities[0].verb.should eql(:likes)
16
+ end
17
+
18
+ it "allows the verbs given, but no other verbs" do
19
+ @sub.likes(@obj)
20
+ @sub.recommends(@obj)
21
+ lambda { @sub.suggests(@obj) }.should raise_error
22
+ end
23
+
24
+ it "allows options on activities (e.g. a comment)" do
25
+ activities = @sub.comments(@obj, :comment => 'This is a silly comment on Tom')
26
+ activities[0].options['comment'].should eql('This is a silly comment on Tom')
27
+ end
28
+
29
+ it "creates an activity for both subject and object" do
30
+ activities = @sub.likes(@obj)
31
+ activities.select {|a| a.owner == @sub}.should_not be_empty # one activity owned by the subject
32
+ activities.select {|a| a.owner == @obj}.should_not be_empty # ... and one owned by the object
33
+ end
34
+
35
+ it "creates activities for those connected to the subject" do
36
+ mary = Connectable.create(:name => 'Mary')
37
+ mary.connect_to(@sub).save # mary is now connected to Tim
38
+ activities = @sub.likes(@obj)
39
+ activities.select {|a| a.owner == mary}.should_not be_empty # Mary gets an activity, because she's connected to Tim
40
+ end
41
+
42
+ it "does *not* create activities for those the subject is connected to" do
43
+ mary = Connectable.create(:name => 'Mary')
44
+ @sub.connect_to(mary).save # Tim is now connected to Mary
45
+ activities = @sub.likes(@obj)
46
+ # Mary does not get an activity,
47
+ # because she's *not* connected to Tim (Tim is connected to Mary, though)
48
+ activities.select {|a| a.owner == mary}.should be_empty
49
+ end
50
+
51
+ it "creates activities for those connected to the object" do
52
+ anne = Connectable.create(:name => 'Anne')
53
+ anne.connect_to(@obj).save
54
+ activities = @sub.likes(@obj)
55
+ # Anne gets an activity
56
+ activities.select {|a| a.owner == anne}.should_not be_empty
57
+ end
58
+
59
+ it "does *not* create an activity for those the subject is connected to" do
60
+ anne = Connectable.create(:name => 'Anne')
61
+ @obj.connect_to(anne).save
62
+ activities = @sub.likes(@obj)
63
+ # Anne does not get an activity
64
+ activities.select {|a| a.owner == anne}.should be_empty
65
+ end
66
+
67
+ end
@@ -0,0 +1,9 @@
1
+ # subject, verb, object
2
+ # both subject and object are 'acts_as_connectable'
3
+ # examples:
4
+ # - tim likes idea x
5
+ # - tom comments "this is awesome" on idea y
6
+ # - ben bought deal "25% off on xyz"
7
+ #
8
+ # how to indicate that someone has seen an activity already?
9
+ # by having "owner, subject, verb, object", there could be a flag on the activity?
@@ -0,0 +1,33 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ class ActsAsConnectableTest < ActiveSupport::TestCase
4
+
5
+ load_schema
6
+
7
+ def setup
8
+ @connectable1 = Connectable.create(:name => 'Anne')
9
+ @connectable2 = Connectable.create(:name => 'Bob')
10
+ end
11
+
12
+ test "should define collection accessors" do
13
+ c = Connectable.create(:name => 'Tim', :email => '2@test.com')
14
+ assert c.respond_to?(:outgoing_social_connections) == true, "does not respond to outgoing connections getter."
15
+ assert c.respond_to?(:incoming_social_connections) == true, "does not respond to incoming connections getter."
16
+ # etc.
17
+ end
18
+
19
+ test "should not allow nil as 'other'" do
20
+ assert_raise ArgumentError do
21
+ @connectable1.connect_to(nil)
22
+ end
23
+ end
24
+
25
+ test "should allow adding connections" do
26
+ @connectable1.connect_to(@connectable2).save
27
+ assert @connectable1.outgoing_social_connections.count == 1
28
+ assert @connectable1.incoming_social_connections.count == 0
29
+ assert @connectable2.outgoing_social_connections.count == 0
30
+ assert @connectable2.incoming_social_connections.count == 1
31
+ end
32
+
33
+ end
data/test/database.yml ADDED
@@ -0,0 +1,4 @@
1
+ sqlite3:
2
+ :adapter: sqlite3
3
+ # :dbfile: vendor/plugins/social_connections/test/social_connections_plugin.sqlite3.db
4
+ :database: db/social_connections_plugin.sqlite3