social_connections 0.0.1

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