socialization 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/.gitignore +9 -0
- data/Appraisals +11 -0
- data/Gemfile +3 -0
- data/LICENSE +20 -0
- data/README.rdoc +126 -0
- data/Rakefile +16 -0
- data/demo/demo_app/.gitignore +15 -0
- data/demo/demo_app/Gemfile +35 -0
- data/demo/demo_app/README.rdoc +261 -0
- data/demo/demo_app/Rakefile +7 -0
- data/demo/demo_app/app/assets/images/rails.png +0 -0
- data/demo/demo_app/app/assets/javascripts/application.js +15 -0
- data/demo/demo_app/app/assets/stylesheets/application.css +13 -0
- data/demo/demo_app/app/controllers/application_controller.rb +3 -0
- data/demo/demo_app/app/helpers/application_helper.rb +2 -0
- data/demo/demo_app/app/mailers/.gitkeep +0 -0
- data/demo/demo_app/app/models/.gitkeep +0 -0
- data/demo/demo_app/app/models/celebrity.rb +4 -0
- data/demo/demo_app/app/models/follow.rb +3 -0
- data/demo/demo_app/app/models/like.rb +3 -0
- data/demo/demo_app/app/models/movie.rb +3 -0
- data/demo/demo_app/app/models/user.rb +6 -0
- data/demo/demo_app/app/views/layouts/application.html.erb +14 -0
- data/demo/demo_app/config.ru +4 -0
- data/demo/demo_app/config/application.rb +59 -0
- data/demo/demo_app/config/boot.rb +6 -0
- data/demo/demo_app/config/database.yml +25 -0
- data/demo/demo_app/config/environment.rb +5 -0
- data/demo/demo_app/config/environments/development.rb +37 -0
- data/demo/demo_app/config/environments/production.rb +67 -0
- data/demo/demo_app/config/environments/test.rb +37 -0
- data/demo/demo_app/config/initializers/backtrace_silencers.rb +7 -0
- data/demo/demo_app/config/initializers/inflections.rb +15 -0
- data/demo/demo_app/config/initializers/mime_types.rb +5 -0
- data/demo/demo_app/config/initializers/secret_token.rb +7 -0
- data/demo/demo_app/config/initializers/session_store.rb +8 -0
- data/demo/demo_app/config/initializers/wrap_parameters.rb +14 -0
- data/demo/demo_app/config/locales/en.yml +5 -0
- data/demo/demo_app/config/routes.rb +58 -0
- data/demo/demo_app/db/migrate/20120115051222_create_users.rb +9 -0
- data/demo/demo_app/db/migrate/20120115051234_create_movies.rb +9 -0
- data/demo/demo_app/db/migrate/20120115051255_create_celebrities.rb +9 -0
- data/demo/demo_app/db/migrate/20120115054646_create_follows.rb +14 -0
- data/demo/demo_app/db/migrate/20120115054647_create_likes.rb +14 -0
- data/demo/demo_app/db/schema.rb +56 -0
- data/demo/demo_app/db/seeds.rb +14 -0
- data/demo/demo_app/doc/README_FOR_APP +2 -0
- data/demo/demo_app/lib/assets/.gitkeep +0 -0
- data/demo/demo_app/lib/tasks/.gitkeep +0 -0
- data/demo/demo_app/public/404.html +26 -0
- data/demo/demo_app/public/422.html +26 -0
- data/demo/demo_app/public/500.html +25 -0
- data/demo/demo_app/public/favicon.ico +0 -0
- data/demo/demo_app/public/index.html +204 -0
- data/demo/demo_app/public/robots.txt +5 -0
- data/demo/demo_app/script/rails +6 -0
- data/demo/demo_app/test/fixtures/.gitkeep +0 -0
- data/demo/demo_app/test/fixtures/celebrities.yml +7 -0
- data/demo/demo_app/test/fixtures/movies.yml +7 -0
- data/demo/demo_app/test/fixtures/users.yml +7 -0
- data/demo/demo_app/test/functional/.gitkeep +0 -0
- data/demo/demo_app/test/integration/.gitkeep +0 -0
- data/demo/demo_app/test/performance/browsing_test.rb +12 -0
- data/demo/demo_app/test/test_helper.rb +13 -0
- data/demo/demo_app/test/unit/.gitkeep +0 -0
- data/demo/demo_app/test/unit/celebrity_test.rb +7 -0
- data/demo/demo_app/test/unit/movie_test.rb +7 -0
- data/demo/demo_app/test/unit/user_test.rb +7 -0
- data/demo/demo_app/vendor/assets/javascripts/.gitkeep +0 -0
- data/demo/demo_app/vendor/assets/stylesheets/.gitkeep +0 -0
- data/demo/demo_app/vendor/plugins/.gitkeep +0 -0
- data/generators/socialization/USAGE +1 -0
- data/generators/socialization/socialization_generator.rb +11 -0
- data/generators/socialization/templates/migration_follows.rb +14 -0
- data/generators/socialization/templates/migration_likes.rb +14 -0
- data/generators/socialization/templates/model_follow.rb +3 -0
- data/generators/socialization/templates/model_like.rb +3 -0
- data/init.rb +1 -0
- data/lib/generators/socialization/socialization_generator.rb +23 -0
- data/lib/socialization.rb +6 -0
- data/lib/socialization/follow_store.rb +14 -0
- data/lib/socialization/followable.rb +23 -0
- data/lib/socialization/follower.rb +31 -0
- data/lib/socialization/hello.rb +37 -0
- data/lib/socialization/like_store.rb +14 -0
- data/lib/socialization/likeable.rb +23 -0
- data/lib/socialization/liker.rb +31 -0
- data/lib/socialization/version.rb +3 -0
- data/socialization.gemspec +29 -0
- data/test/follow_test.rb +37 -0
- data/test/like_test.rb +46 -0
- data/test/test_helper.rb +67 -0
- metadata +200 -0
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
require 'test_helper'
|
|
2
|
+
require 'rails/performance_test_help'
|
|
3
|
+
|
|
4
|
+
class BrowsingTest < ActionDispatch::PerformanceTest
|
|
5
|
+
# Refer to the documentation for all available options
|
|
6
|
+
# self.profile_options = { :runs => 5, :metrics => [:wall_time, :memory]
|
|
7
|
+
# :output => 'tmp/performance', :formats => [:flat] }
|
|
8
|
+
|
|
9
|
+
def test_homepage
|
|
10
|
+
get '/'
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
ENV["RAILS_ENV"] = "test"
|
|
2
|
+
require File.expand_path('../../config/environment', __FILE__)
|
|
3
|
+
require 'rails/test_help'
|
|
4
|
+
|
|
5
|
+
class ActiveSupport::TestCase
|
|
6
|
+
# Setup all fixtures in test/fixtures/*.(yml|csv) for all tests in alphabetical order.
|
|
7
|
+
#
|
|
8
|
+
# Note: You'll currently still have to declare fixtures explicitly in integration tests
|
|
9
|
+
# -- they do not yet inherit this setting
|
|
10
|
+
fixtures :all
|
|
11
|
+
|
|
12
|
+
# Add more helper methods to be used by all tests here...
|
|
13
|
+
end
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Generates both the Follow model class and the migration to create its table.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
class SocializationGenerator < Rails::Generator::Base
|
|
2
|
+
def manifest
|
|
3
|
+
record do |m|
|
|
4
|
+
m.template 'model_follow.rb', 'app/models/follow.rb'
|
|
5
|
+
m.template 'model_like.rb', 'app/models/like.rb'
|
|
6
|
+
m.migration_template 'migration_follows.rb', 'db/migrate', :migration_file_name => 'create_follows'
|
|
7
|
+
sleep 1 # force unique migration timestamp
|
|
8
|
+
m.migration_template 'migration_likes.rb', 'db/migrate', :migration_file_name => 'create_likes'
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
class CreateFollows < ActiveRecord::Migration
|
|
2
|
+
def change
|
|
3
|
+
create_table :follows do |t|
|
|
4
|
+
t.string :follower_type
|
|
5
|
+
t.integer :follower_id
|
|
6
|
+
t.string :followable_type
|
|
7
|
+
t.integer :followable_id
|
|
8
|
+
t.datetime :created_at
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
add_index :follows, ["follower_id", "follower_type"], :name => "fk_follows"
|
|
12
|
+
add_index :follows, ["followable_id", "followable_type"], :name => "fk_followables"
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
class CreateLikes < ActiveRecord::Migration
|
|
2
|
+
def change
|
|
3
|
+
create_table :likes do |t|
|
|
4
|
+
t.string :liker_type
|
|
5
|
+
t.integer :liker_id
|
|
6
|
+
t.string :likeable_type
|
|
7
|
+
t.integer :likeable_id
|
|
8
|
+
t.datetime :created_at
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
add_index :likes, ["liker_id", "liker_type"], :name => "fk_likes"
|
|
12
|
+
add_index :likes, ["likeable_id", "likeable_type"], :name => "fk_likeables"
|
|
13
|
+
end
|
|
14
|
+
end
|
data/init.rb
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
require 'socialization'
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
require 'rails/generators'
|
|
2
|
+
require 'rails/generators/migration'
|
|
3
|
+
|
|
4
|
+
class SocializationGenerator < Rails::Generators::Base
|
|
5
|
+
include Rails::Generators::Migration
|
|
6
|
+
source_root File.expand_path(File.join('..', '..', '..', 'generators', 'socialization', 'templates'), File.dirname(__FILE__))
|
|
7
|
+
|
|
8
|
+
def self.next_migration_number(dirname)
|
|
9
|
+
if ActiveRecord::Base.timestamped_migrations
|
|
10
|
+
Time.now.utc.strftime("%Y%m%d%H%M%S")
|
|
11
|
+
else
|
|
12
|
+
"%.3d" % (current_migration_number(dirname) + 1)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def create_migration_file
|
|
17
|
+
copy_file 'model_follow.rb', 'app/models/follow.rb'
|
|
18
|
+
copy_file 'model_like.rb', 'app/models/like.rb'
|
|
19
|
+
migration_template 'migration_follows.rb', 'db/migrate/create_follows.rb'
|
|
20
|
+
sleep 1 # wait a second to have a unique migration timestamp
|
|
21
|
+
migration_template 'migration_likes.rb', 'db/migrate/create_likes.rb'
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
module Socialization
|
|
2
|
+
module FollowStore
|
|
3
|
+
def self.included(base)
|
|
4
|
+
base.class_eval do
|
|
5
|
+
belongs_to :follower, :polymorphic => true
|
|
6
|
+
belongs_to :followable, :polymorphic => true
|
|
7
|
+
|
|
8
|
+
validates_uniqueness_of :followable_type, :scope => [:followable_id, :follower_type, :follower_id], :message => 'You cannot follow the same thing twice.'
|
|
9
|
+
|
|
10
|
+
def self.human_attribute_name(*args); ''; end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module Socialization
|
|
2
|
+
module Followable
|
|
3
|
+
def self.included(base)
|
|
4
|
+
base.class_eval do
|
|
5
|
+
# A following is the Follow record of the follower following self.
|
|
6
|
+
has_many :followings, :as => :followable, :dependent => :destroy, :class_name => 'Follow'
|
|
7
|
+
|
|
8
|
+
def is_followable?
|
|
9
|
+
true
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def followed_by?(follower)
|
|
13
|
+
raise ArgumentError, "#{follower} is not a follower!" unless follower.respond_to?(:is_follower?) && follower.is_follower?
|
|
14
|
+
!self.followings.where(:follower_type => follower.class.to_s, :follower_id => follower.id).pluck("1").empty?
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def followers
|
|
18
|
+
self.followings.map { |f| f.follower }
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
module Socialization
|
|
2
|
+
module Follower
|
|
3
|
+
def self.included(base)
|
|
4
|
+
base.class_eval do
|
|
5
|
+
# A follow is the Follow record of self following a followable record.
|
|
6
|
+
has_many :follows, :as => :follower, :dependent => :destroy, :class_name => 'Follow'
|
|
7
|
+
|
|
8
|
+
def is_follower?
|
|
9
|
+
true
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def follow!(followable)
|
|
13
|
+
raise ArgumentError, "#{followable} is not followable!" unless followable.respond_to?(:is_followable?)
|
|
14
|
+
Follow.create! follower: self, followable: followable
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def unfollow!(followable)
|
|
18
|
+
followable.followers.where(:follower => self).each do |f|
|
|
19
|
+
f.destroy
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def follows?(followable)
|
|
24
|
+
raise ArgumentError, "#{followable} is not followable!" unless followable.respond_to?(:is_followable?) && followable.is_followable?
|
|
25
|
+
!self.follows.where(:followable_type => followable.class.to_s, :followable_id => followable.id).pluck("1").empty?
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
%w{followable follower follow_store likeable liker like_store}.each do |f|
|
|
2
|
+
require "#{File.dirname(__FILE__)}/#{f}"
|
|
3
|
+
end
|
|
4
|
+
|
|
5
|
+
module Socialization
|
|
6
|
+
module Hello
|
|
7
|
+
def self.included(klass)
|
|
8
|
+
klass.send(:extend, ClassMethods)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
module ClassMethods
|
|
12
|
+
def acts_as_follower(opts = nil)
|
|
13
|
+
include Socialization::Follower
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def acts_as_followable(opts = nil)
|
|
17
|
+
include Socialization::Followable
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def acts_as_follow_store(opts = nil)
|
|
21
|
+
include Socialization::FollowStore
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def acts_as_liker(opts = nil)
|
|
25
|
+
include Socialization::Liker
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def acts_as_likeable(opts = nil)
|
|
29
|
+
include Socialization::Likeable
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def acts_as_like_store(opts = nil)
|
|
33
|
+
include Socialization::LikeStore
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
module Socialization
|
|
2
|
+
module LikeStore
|
|
3
|
+
def self.included(base)
|
|
4
|
+
base.class_eval do
|
|
5
|
+
belongs_to :liker, :polymorphic => true
|
|
6
|
+
belongs_to :likeable, :polymorphic => true
|
|
7
|
+
|
|
8
|
+
validates_uniqueness_of :likeable_type, :scope => [:likeable_id, :liker_type, :liker_id], :message => 'You cannot like the same thing twice.'
|
|
9
|
+
|
|
10
|
+
def self.human_attribute_name(*args); ''; end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module Socialization
|
|
2
|
+
module Likeable
|
|
3
|
+
def self.included(base)
|
|
4
|
+
base.class_eval do
|
|
5
|
+
# A liking is the Like record of the liker liking self.
|
|
6
|
+
has_many :likings, :as => :likeable, :dependent => :destroy, :class_name => 'Like'
|
|
7
|
+
|
|
8
|
+
def is_likeable?
|
|
9
|
+
true
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def liked_by?(liker)
|
|
13
|
+
raise ArgumentError, "#{liker} is not a liker!" unless liker.respond_to?(:is_liker?) && liker.is_liker?
|
|
14
|
+
!self.likings.where(:liker_type => liker.class.to_s, :liker_id => liker.id).pluck("1").empty?
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def likers
|
|
18
|
+
self.likings.map { |l| l.liker }
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
module Socialization
|
|
2
|
+
module Liker
|
|
3
|
+
def self.included(base)
|
|
4
|
+
base.class_eval do
|
|
5
|
+
# A like is the Like record of self liking a likeable record.
|
|
6
|
+
has_many :likes, :as => :liker, :dependent => :destroy, :class_name => 'Like'
|
|
7
|
+
|
|
8
|
+
def is_liker?
|
|
9
|
+
true
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def like!(likeable)
|
|
13
|
+
raise ArgumentError, "#{likeable} is not likeable!" unless likeable.respond_to?(:is_likeable?)
|
|
14
|
+
Like.create! liker: self, likeable: likeable
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def unlike!(likeable)
|
|
18
|
+
likeable.likers.where(:liker => self).each do |l|
|
|
19
|
+
l.destroy
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def likes?(likeable)
|
|
24
|
+
raise ArgumentError, "#{likeable} is not likeable!" unless likeable.respond_to?(:is_likeable?) && likeable.is_likeable?
|
|
25
|
+
!self.likes.where(:likeable_type => likeable.class.to_s, :likeable_id => likeable.id).pluck("1").empty?
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
|
3
|
+
require "socialization/version"
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |s|
|
|
6
|
+
s.name = "socialization"
|
|
7
|
+
s.version = Socialization::VERSION
|
|
8
|
+
s.authors = ["Carl Mercier"]
|
|
9
|
+
s.email = "carl@carlmercier.com"
|
|
10
|
+
s.homepage = "https://github.com/cmer/socialization"
|
|
11
|
+
s.summary = "Easily socialize your app with Likes and Follows"
|
|
12
|
+
s.description = "Socialization allow any model to Follow and/or Like any other model. This is accomplished through a double polymorphic relationship on the Follow and Like models. But you don't need to know that since all the complexity is hidden from you."
|
|
13
|
+
|
|
14
|
+
s.files = `git ls-files`.split("\n")
|
|
15
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
|
16
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
|
17
|
+
s.require_paths = ["lib"]
|
|
18
|
+
|
|
19
|
+
s.has_rdoc = true
|
|
20
|
+
s.extra_rdoc_files = ["README.rdoc"]
|
|
21
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
|
22
|
+
|
|
23
|
+
s.add_runtime_dependency "activerecord"
|
|
24
|
+
|
|
25
|
+
s.add_development_dependency "appraisal"
|
|
26
|
+
s.add_development_dependency "logger"
|
|
27
|
+
s.add_development_dependency "mocha"
|
|
28
|
+
s.add_development_dependency "sqlite3"
|
|
29
|
+
end
|
data/test/follow_test.rb
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
require File.dirname(__FILE__)+'/test_helper'
|
|
2
|
+
|
|
3
|
+
class FollowTest < Test::Unit::TestCase
|
|
4
|
+
def setup
|
|
5
|
+
@u_john = User.create name: 'John Doe'
|
|
6
|
+
@u_jane = User.create name: 'Jane Doe'
|
|
7
|
+
@c_chuck = Celebrity.create name: 'Chuck Norris'
|
|
8
|
+
@c_uma = Celebrity.create name: 'Uma Thurman'
|
|
9
|
+
@c_rick = Celebrity.create name: 'Rick Astley'
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def test_the_world
|
|
13
|
+
assert @u_john.is_follower?
|
|
14
|
+
assert @c_chuck.is_followable?
|
|
15
|
+
|
|
16
|
+
assert @u_john.follow!(@c_rick)
|
|
17
|
+
assert @u_john.follow!(@c_chuck)
|
|
18
|
+
assert @u_jane.follow!(@c_rick)
|
|
19
|
+
|
|
20
|
+
assert_equal true, @u_john.follows?(@c_chuck)
|
|
21
|
+
assert_equal true, @u_john.follows?(@c_rick)
|
|
22
|
+
assert_equal true, @c_chuck.followed_by?(@u_john)
|
|
23
|
+
|
|
24
|
+
assert @c_uma.followers.empty?
|
|
25
|
+
|
|
26
|
+
# can't have duplicate follows
|
|
27
|
+
assert_raise ActiveRecord::RecordInvalid do
|
|
28
|
+
@u_john.follow!(@c_rick)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def test_user_following_user
|
|
33
|
+
@u_john.follow!(@u_jane)
|
|
34
|
+
assert_equal true, @u_john.follows?(@u_jane)
|
|
35
|
+
assert_equal false, @u_jane.follows?(@u_john)
|
|
36
|
+
end
|
|
37
|
+
end
|