partisan 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.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/Gemfile +2 -0
- data/LICENSE.txt +22 -0
- data/README.md +101 -0
- data/Rakefile +11 -0
- data/lib/generators/partisan/USAGE +3 -0
- data/lib/generators/partisan/install_generator.rb +25 -0
- data/lib/generators/partisan/templates/migration.rb +17 -0
- data/lib/partisan.rb +15 -0
- data/lib/partisan/follow.rb +24 -0
- data/lib/partisan/follow_helper.rb +15 -0
- data/lib/partisan/followable.rb +70 -0
- data/lib/partisan/follower.rb +113 -0
- data/lib/partisan/railtie.rb +18 -0
- data/lib/partisan/version.rb +3 -0
- data/partisan.gemspec +27 -0
- data/spec/followable_spec.rb +74 -0
- data/spec/follower_spec.rb +89 -0
- data/spec/spec_helper.rb +30 -0
- data/spec/support/extensions/active_record.rb +9 -0
- data/spec/support/macros/database_macros.rb +39 -0
- data/spec/support/macros/model_macros.rb +26 -0
- metadata +145 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 915b00e7ddd9b09bc6a7e260e56d475a45b8daaf
|
4
|
+
data.tar.gz: affbbc560d7a023b40b85216ac65b035490a793c
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 3d1c0843ee2e7ce9c1558490f6ea37b6fdf3911a0af79959f1343a627163ec430293951233104b67438bdd59414fe32519054684d16321a64d9aec97ddf71ef1
|
7
|
+
data.tar.gz: df350de0310c0a71e68bb752d8986afc74fa0e0f5f878aefbde51782f37933c059b11dbb6b8d797ce683a68a0c9b9ffc2bc751a70dae56ae80fd7298119b9257
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Rémi Prévost
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
# Partisan
|
2
|
+
|
3
|
+
Partisan is a Ruby library that allows ActiveRecord records to be follower and followable
|
4
|
+
|
5
|
+
It’s heavily inspired by `acts_as_follower`.
|
6
|
+
|
7
|
+
It’s not 100% compatible with `acts_as_follower`, I removed some "features":
|
8
|
+
|
9
|
+
* block follower
|
10
|
+
* Array with all types of followers/following
|
11
|
+
* `*_count` methods
|
12
|
+
|
13
|
+
But I also added awesome new ones:
|
14
|
+
|
15
|
+
* model_follower_fields: So you can do `following_team_ids` but also `following_team_names`. It takes advantage of the `pluck` method, so it doesn’t create an instance of each follower. (go check `pluck` documentation, it’s simply awesome).
|
16
|
+
|
17
|
+
* followers/followings now returns ActiveRecord::Relation for easy chaining/scoping/paginating...
|
18
|
+
|
19
|
+
## Installation
|
20
|
+
|
21
|
+
Add this line to your application’s Gemfile:
|
22
|
+
|
23
|
+
```ruby
|
24
|
+
gem 'partisan'
|
25
|
+
```
|
26
|
+
|
27
|
+
And then execute:
|
28
|
+
|
29
|
+
```bash
|
30
|
+
$ bundle
|
31
|
+
```
|
32
|
+
|
33
|
+
Run the migration to add the `follows` table:
|
34
|
+
|
35
|
+
```bash
|
36
|
+
$ rails generate partisan:install
|
37
|
+
```
|
38
|
+
|
39
|
+
## Usage
|
40
|
+
|
41
|
+
Create a couple of models.
|
42
|
+
|
43
|
+
```ruby
|
44
|
+
class Fan < ActiveRecord::Base
|
45
|
+
acts_as_follower
|
46
|
+
end
|
47
|
+
|
48
|
+
class Band < ActiveRecord::Base
|
49
|
+
acts_as_followable
|
50
|
+
end
|
51
|
+
```
|
52
|
+
|
53
|
+
And follow/unfollow other records!
|
54
|
+
|
55
|
+
```ruby
|
56
|
+
fan = Fan.find(1)
|
57
|
+
band = Band.find(2)
|
58
|
+
|
59
|
+
fan.follow(band)
|
60
|
+
fan.following_bands
|
61
|
+
# => [<Band id=2>]
|
62
|
+
|
63
|
+
fan.following?(band)
|
64
|
+
# => true
|
65
|
+
|
66
|
+
fan.unfollow(band)
|
67
|
+
fan.following?(band)
|
68
|
+
# => false
|
69
|
+
|
70
|
+
```
|
71
|
+
|
72
|
+
Most of the times, you would want to get a quick look at about how many bands followed a certain resource. That could be an expensive operation.
|
73
|
+
|
74
|
+
However, if the *followed* record has an `followers_count` column, Partisan will populate its value with how many bands followed that record.
|
75
|
+
|
76
|
+
```ruby
|
77
|
+
band.follow(band)
|
78
|
+
|
79
|
+
band.followers.count
|
80
|
+
# SQL query that counts records and returns `1`
|
81
|
+
|
82
|
+
band.followers_count
|
83
|
+
# Quick lookup into the column and returns `1`
|
84
|
+
```
|
85
|
+
|
86
|
+
The same concept applies to `followable` with a `following_count` column
|
87
|
+
|
88
|
+
## License
|
89
|
+
|
90
|
+
`Partisan` is © 2013 [Mirego](http://www.mirego.com) and may be freely distributed under the [New BSD license](http://opensource.org/licenses/BSD-3-Clause). See the [`LICENSE.md`](https://github.com/mirego/emotions/blob/master/LICENSE.md) file.
|
91
|
+
|
92
|
+
## About Mirego
|
93
|
+
|
94
|
+
Mirego is a team of passionate people who believe that work is a place where you can innovate and have fun.
|
95
|
+
We proudly built mobile applications for
|
96
|
+
[iPhone](http://mirego.com/en/iphone-app-development/ "iPhone application development"),
|
97
|
+
[iPad](http://mirego.com/en/ipad-app-development/ "iPad application development"),
|
98
|
+
[Android](http://mirego.com/en/android-app-development/ "Android application development"),
|
99
|
+
[Blackberry](http://mirego.com/en/blackberry-app-development/ "Blackberry application development"),
|
100
|
+
[Windows Phone](http://mirego.com/en/windows-phone-app-development/ "Windows Phone application development") and
|
101
|
+
[Windows 8](http://mirego.com/en/windows-8-app-development/ "Windows 8 application development").
|
data/Rakefile
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'rails/generators'
|
2
|
+
require 'rails/generators/migration'
|
3
|
+
|
4
|
+
module Partisan
|
5
|
+
module Generators
|
6
|
+
class InstallGenerator < Rails::Generators::Base
|
7
|
+
include Rails::Generators::Migration
|
8
|
+
source_root File.expand_path('../templates', __FILE__)
|
9
|
+
|
10
|
+
# Implement the required interface for Rails::Generators::Migration.
|
11
|
+
# taken from http://github.com/rails/rails/blob/master/activerecord/lib/generators/active_record.rb
|
12
|
+
def self.next_migration_number(dirname)
|
13
|
+
if ActiveRecord::Base.timestamped_migrations
|
14
|
+
Time.now.utc.strftime("%Y%m%d%H%M%S")
|
15
|
+
else
|
16
|
+
"%.3d" % (current_migration_number(dirname) + 1)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def create_migration_file
|
21
|
+
migration_template 'migration.rb', 'db/migrate/add_follows_migration.rb'
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class AddFollowsMigration < ActiveRecord::Migration
|
2
|
+
def up
|
3
|
+
create_table :follows do |t|
|
4
|
+
t.references :followable, polymorphic: true
|
5
|
+
t.references :follower, polymorphic: true
|
6
|
+
|
7
|
+
t.timestamps
|
8
|
+
end
|
9
|
+
|
10
|
+
add_index :follows, ["follower_id", "follower_type"], name: "fk_follows"
|
11
|
+
add_index :follows, ["followable_id", "followable_type"], name: "fk_followables"
|
12
|
+
end
|
13
|
+
|
14
|
+
def down
|
15
|
+
drop_table :follows
|
16
|
+
end
|
17
|
+
end
|
data/lib/partisan.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require "partisan/version"
|
2
|
+
|
3
|
+
require "active_record"
|
4
|
+
|
5
|
+
require "partisan/follow"
|
6
|
+
require "partisan/follow_helper"
|
7
|
+
require "partisan/follower"
|
8
|
+
require "partisan/followable"
|
9
|
+
|
10
|
+
module Partisan
|
11
|
+
|
12
|
+
include Partisan::FollowHelper
|
13
|
+
end
|
14
|
+
|
15
|
+
require 'partisan/railtie' if defined?(Rails) && Rails::VERSION::MAJOR >= 3
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Partisan
|
2
|
+
class Follow < ActiveRecord::Base
|
3
|
+
self.table_name = 'follows'
|
4
|
+
|
5
|
+
# Validations
|
6
|
+
validates :followable, presence: true
|
7
|
+
validates :follower, presence: true
|
8
|
+
|
9
|
+
# Associations
|
10
|
+
belongs_to :followable, polymorphic: true
|
11
|
+
belongs_to :follower, polymorphic: true
|
12
|
+
|
13
|
+
# Callbacks
|
14
|
+
after_create :update_follow_counter
|
15
|
+
after_destroy :update_follow_counter
|
16
|
+
|
17
|
+
protected
|
18
|
+
|
19
|
+
def update_follow_counter
|
20
|
+
self.follower.update_follow_counter
|
21
|
+
self.followable.update_follow_counter
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Partisan
|
2
|
+
module FollowHelper
|
3
|
+
|
4
|
+
private
|
5
|
+
|
6
|
+
# Retrieves the parent class name if using STI.
|
7
|
+
def parent_class_name(obj)
|
8
|
+
klass = obj.class
|
9
|
+
klass = klass.superclass while klass.superclass != ActiveRecord::Base
|
10
|
+
|
11
|
+
klass.name
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module Partisan
|
2
|
+
module Followable
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
include Partisan::FollowHelper
|
5
|
+
|
6
|
+
included do
|
7
|
+
has_many :followings, as: :followable, class_name: 'Partisan::Follow', dependent: :destroy
|
8
|
+
end
|
9
|
+
|
10
|
+
# Return true or false if the resource is following another
|
11
|
+
#
|
12
|
+
# @example
|
13
|
+
#
|
14
|
+
# @team.followed_by?(@user)
|
15
|
+
#
|
16
|
+
# @return (Boolean)
|
17
|
+
def followed_by?(resource)
|
18
|
+
resource.following?(self)
|
19
|
+
end
|
20
|
+
|
21
|
+
# Get resource records for a specific type, used by #method_missing
|
22
|
+
# It conveniently returns an ActiveRecord::Relation for easy chaining of useful ActiveRecord methods
|
23
|
+
#
|
24
|
+
# @example
|
25
|
+
#
|
26
|
+
# @team.followers_by_type('User')
|
27
|
+
#
|
28
|
+
# @return (ActiveRecord::Relation)
|
29
|
+
def followers_by_type(follower_type)
|
30
|
+
opts = {
|
31
|
+
'follows.followable_id' => self.id,
|
32
|
+
'follows.followable_type' => parent_class_name(self)
|
33
|
+
}
|
34
|
+
|
35
|
+
follower_type.constantize.joins(:follows).where(opts)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Get ids of resources following self
|
39
|
+
# Use #pluck for an optimized sql query
|
40
|
+
#
|
41
|
+
# @example
|
42
|
+
#
|
43
|
+
# @team.following_ids_by_type('User')
|
44
|
+
#
|
45
|
+
# @return (Array)
|
46
|
+
def follower_fields_by_type(follower_type, follower_field)
|
47
|
+
followers_by_type(follower_type).pluck("#{follower_type.tableize}.#{follower_field}")
|
48
|
+
end
|
49
|
+
|
50
|
+
# Update cache counter
|
51
|
+
# Called in after_create and after_destroy
|
52
|
+
def update_follow_counter
|
53
|
+
self.update_attribute('followers_count', self.followings.count) if self.respond_to?(:followers_count)
|
54
|
+
end
|
55
|
+
|
56
|
+
def method_missing(m, *args)
|
57
|
+
if m.to_s[/(.+)_follower_(.+)s$/]
|
58
|
+
follower_fields_by_type($1.singularize.classify, $2)
|
59
|
+
elsif m.to_s[/(.+)_followers$/]
|
60
|
+
followers_by_type($1.singularize.classify)
|
61
|
+
else
|
62
|
+
super
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def respond_to?(m, include_private = false)
|
67
|
+
super || m.to_s[/(.+)_follower_(.+)s$/] || m.to_s[/(.+)_follower/]
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
module Partisan
|
2
|
+
module Follower
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
include Partisan::FollowHelper
|
5
|
+
|
6
|
+
included do
|
7
|
+
has_many :follows, as: :follower, class_name: 'Partisan::Follow', dependent: :destroy
|
8
|
+
end
|
9
|
+
|
10
|
+
# Add follow record if it doesn’t exist
|
11
|
+
#
|
12
|
+
# @example
|
13
|
+
#
|
14
|
+
# @user.follow(@team)
|
15
|
+
#
|
16
|
+
# @return (Partisan::Follow)
|
17
|
+
def follow(resource)
|
18
|
+
return if self == resource
|
19
|
+
|
20
|
+
fetch_follows(resource).first_or_create
|
21
|
+
end
|
22
|
+
alias_method :start_following, :follow
|
23
|
+
|
24
|
+
# Delete follow record if it exists
|
25
|
+
#
|
26
|
+
# @example
|
27
|
+
#
|
28
|
+
# @user.unfollow(@team)
|
29
|
+
#
|
30
|
+
# @return (Partisan::Follow) || nil
|
31
|
+
def unfollow(resource)
|
32
|
+
return if self == resource
|
33
|
+
|
34
|
+
record = fetch_follows(resource).first
|
35
|
+
record.try(:destroy)
|
36
|
+
end
|
37
|
+
alias_method :stop_following, :unfollow
|
38
|
+
|
39
|
+
# Return true or false if the resource is following another
|
40
|
+
#
|
41
|
+
# @example
|
42
|
+
#
|
43
|
+
# @user.following?(@team)
|
44
|
+
#
|
45
|
+
# @return (Boolean)
|
46
|
+
def following?(resource)
|
47
|
+
return false if self == resource
|
48
|
+
|
49
|
+
fetch_follows(resource).exists?
|
50
|
+
end
|
51
|
+
|
52
|
+
# Get all follows record related to a resource
|
53
|
+
#
|
54
|
+
# @example
|
55
|
+
#
|
56
|
+
# @user.fetch_follows(@team)
|
57
|
+
#
|
58
|
+
# @return [Partisan::Follow, ...]
|
59
|
+
def fetch_follows(resource)
|
60
|
+
follows.where followable_id: resource.id, followable_type: parent_class_name(resource)
|
61
|
+
end
|
62
|
+
|
63
|
+
# Get resource records for a specific type, used by #method_missing
|
64
|
+
# It conveniently returns an ActiveRecord::Relation for easy chaining of useful ActiveRecord methods
|
65
|
+
#
|
66
|
+
# @example
|
67
|
+
#
|
68
|
+
# @user.following_by_type('Team')
|
69
|
+
#
|
70
|
+
# @return (ActiveRecord::Relation)
|
71
|
+
def following_by_type(followable_type)
|
72
|
+
opts = {
|
73
|
+
'follows.follower_id' => self.id,
|
74
|
+
'follows.follower_type' => parent_class_name(self)
|
75
|
+
}
|
76
|
+
|
77
|
+
followable_type.constantize.joins(:followings).where(opts)
|
78
|
+
end
|
79
|
+
|
80
|
+
# Get ids of resources following self
|
81
|
+
# Use #pluck for an optimized sql query
|
82
|
+
#
|
83
|
+
# @example
|
84
|
+
#
|
85
|
+
# @user.following_ids_by_type('Team')
|
86
|
+
#
|
87
|
+
# @return (Array)
|
88
|
+
def following_fields_by_type(followable_type, followable_field)
|
89
|
+
following_by_type(followable_type).pluck("#{followable_type.tableize}.#{followable_field}")
|
90
|
+
end
|
91
|
+
|
92
|
+
# Update cache counter
|
93
|
+
# Called in after_create and after_destroy
|
94
|
+
def update_follow_counter
|
95
|
+
self.update_attribute('followings_count', self.follows.count) if self.respond_to?(:followings_count)
|
96
|
+
end
|
97
|
+
|
98
|
+
def method_missing(m, *args)
|
99
|
+
if m.to_s[/following_(.+)_(.+)s$/]
|
100
|
+
following_fields_by_type($1.singularize.classify, $2)
|
101
|
+
elsif m.to_s[/following_(.+)/]
|
102
|
+
following_by_type($1.singularize.classify)
|
103
|
+
else
|
104
|
+
super
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def respond_to?(m, include_private = false)
|
109
|
+
super || m.to_s[/following_(.+)_(.+)s$/] || m.to_s[/following_(.+)/]
|
110
|
+
end
|
111
|
+
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'acts_as_follower'
|
2
|
+
require 'rails'
|
3
|
+
|
4
|
+
module Partisan
|
5
|
+
class Railtie < Rails::Railtie
|
6
|
+
initializer "follows.active_record" do |app|
|
7
|
+
ActiveSupport.on_load :active_record do
|
8
|
+
def self.acts_as_follower
|
9
|
+
self.send :include, Partisan::Follower
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.acts_as_followable
|
13
|
+
self.send :include, Partisan::Followable
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/partisan.gemspec
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'partisan/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'partisan'
|
8
|
+
spec.version = Partisan::VERSION
|
9
|
+
spec.authors = ['Simon Prévost']
|
10
|
+
spec.email = ['sprevost@mirego.com']
|
11
|
+
spec.description = 'Partisan is a Ruby library that allows ActiveRecord records to be follower and followable, just like on popular social networks. It’s heavily inspired by the origin acts_as_follower which is no longer maintened'
|
12
|
+
spec.summary = 'Partisan is a Ruby library that allows ActiveRecord records to be follower and followable'
|
13
|
+
spec.homepage = 'https://github.com/simonprev/partisan'
|
14
|
+
spec.license = 'BSD 3-Clause'
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(spec)/})
|
19
|
+
spec.require_paths = ['lib']
|
20
|
+
|
21
|
+
spec.add_dependency 'rails', '>= 3.0.0'
|
22
|
+
|
23
|
+
spec.add_development_dependency 'bundler', '~> 1.3'
|
24
|
+
spec.add_development_dependency 'rake'
|
25
|
+
spec.add_development_dependency 'rspec'
|
26
|
+
spec.add_development_dependency 'sqlite3'
|
27
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), "spec_helper.rb")
|
2
|
+
|
3
|
+
describe Partisan::Followable do
|
4
|
+
before do
|
5
|
+
run_migration do
|
6
|
+
create_table(:fans, force: true)
|
7
|
+
create_table(:users, force: true)
|
8
|
+
create_table(:concerts, force: true)
|
9
|
+
|
10
|
+
create_table(:bands, force: true) do |t|
|
11
|
+
t.integer :followers_count, default: 0
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
follower 'Fan'
|
16
|
+
follower 'User'
|
17
|
+
followable 'Band'
|
18
|
+
followable 'Concert'
|
19
|
+
end
|
20
|
+
|
21
|
+
let(:band) { Band.create }
|
22
|
+
let(:user) { User.create }
|
23
|
+
let(:concert) { Concert.create }
|
24
|
+
let(:fan) { Fan.create }
|
25
|
+
|
26
|
+
describe :InstanceMethods do
|
27
|
+
before do
|
28
|
+
user.follow band
|
29
|
+
|
30
|
+
band.reload
|
31
|
+
end
|
32
|
+
|
33
|
+
describe :followed_by? do
|
34
|
+
it { expect(band.followed_by? user).to be_true }
|
35
|
+
it { expect(concert.followed_by? user).to be_false }
|
36
|
+
end
|
37
|
+
|
38
|
+
describe :followers_by_type do
|
39
|
+
it { expect(band.followers_by_type('User').count).to eq 1 }
|
40
|
+
it { expect(band.followers_by_type('User')).to be_an_instance_of(ActiveRecord::Relation) }
|
41
|
+
it { expect(band.followers_by_type('User').first).to be_an_instance_of(User) }
|
42
|
+
it { expect(band.followers_by_type('Fan').count).to eq 0 }
|
43
|
+
end
|
44
|
+
|
45
|
+
describe :following_fields_by_type do
|
46
|
+
it { expect(band.follower_fields_by_type('User', 'id').count).to eq 1 }
|
47
|
+
it { expect(band.follower_fields_by_type('User', 'id').first).to eq user.id }
|
48
|
+
it { expect(band.follower_fields_by_type('Fan', 'id').count).to eq 0 }
|
49
|
+
end
|
50
|
+
|
51
|
+
describe :followers_by_type_in_method_missing do
|
52
|
+
it { expect(band.user_followers.count).to eq 1 }
|
53
|
+
it { expect(band.user_followers).to be_an_instance_of(ActiveRecord::Relation) }
|
54
|
+
it { expect(band.user_followers.first).to be_an_instance_of(User) }
|
55
|
+
it { expect(band.fan_followers.count).to eq 0 }
|
56
|
+
end
|
57
|
+
|
58
|
+
describe :following_fields_by_type_in_method_missing do
|
59
|
+
it { expect(band.users_follower_ids.count).to eq 1 }
|
60
|
+
it { expect(band.users_follower_ids.first).to eq user.id }
|
61
|
+
it { expect(band.fans_follower_ids.count).to eq 0 }
|
62
|
+
end
|
63
|
+
|
64
|
+
describe :respond_to? do
|
65
|
+
it { expect(band.respond_to?(:user_followers)).to be_true }
|
66
|
+
it { expect(band.respond_to?(:users_follower_ids)).to be_true }
|
67
|
+
end
|
68
|
+
|
69
|
+
describe :update_follow_counter do
|
70
|
+
it { expect(band.followers_count).to eq 1 }
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
@@ -0,0 +1,89 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), "spec_helper.rb")
|
2
|
+
|
3
|
+
describe Partisan::Follower do
|
4
|
+
before do
|
5
|
+
run_migration do
|
6
|
+
create_table(:users, force: true) do |t|
|
7
|
+
t.integer :followings_count, default: 0
|
8
|
+
end
|
9
|
+
create_table(:concerts, force: true)
|
10
|
+
create_table(:bands, force: true)
|
11
|
+
end
|
12
|
+
|
13
|
+
follower 'User'
|
14
|
+
followable 'Band'
|
15
|
+
followable 'Concert'
|
16
|
+
end
|
17
|
+
|
18
|
+
let(:band) { Band.create }
|
19
|
+
let(:user) { User.create }
|
20
|
+
let(:concert) { Concert.create }
|
21
|
+
|
22
|
+
describe :InstanceMethods do
|
23
|
+
before do
|
24
|
+
user.follow band
|
25
|
+
|
26
|
+
user.reload
|
27
|
+
end
|
28
|
+
|
29
|
+
describe :follow do
|
30
|
+
it { expect(Partisan::Follow.last.follower_id).to eq user.id }
|
31
|
+
it { expect(Partisan::Follow.last.followable_id).to eq band.id }
|
32
|
+
end
|
33
|
+
|
34
|
+
describe :unfollow do
|
35
|
+
it { expect{user.unfollow band}.to change{Partisan::Follow.last}.to(nil) }
|
36
|
+
end
|
37
|
+
|
38
|
+
describe :following? do
|
39
|
+
let(:band2) { Band.create }
|
40
|
+
|
41
|
+
it { expect(user.following? band).to be_true }
|
42
|
+
it { expect(user.following? band2).to be_false }
|
43
|
+
end
|
44
|
+
|
45
|
+
describe :following_by_type do
|
46
|
+
it { expect(user.following_by_type('Band').count).to eq 1 }
|
47
|
+
it { expect(user.following_by_type('Band')).to be_an_instance_of(ActiveRecord::Relation) }
|
48
|
+
it { expect(user.following_by_type('Band').first).to be_an_instance_of(Band) }
|
49
|
+
it { expect(user.following_by_type('Concert').count).to eq 0 }
|
50
|
+
end
|
51
|
+
|
52
|
+
describe :following_fields_by_type do
|
53
|
+
it { expect(user.following_fields_by_type('Band', 'id').count).to eq 1 }
|
54
|
+
it { expect(user.following_fields_by_type('Band', 'id').first).to eq band.id }
|
55
|
+
it { expect(user.following_fields_by_type('Concert', 'id').count).to eq 0 }
|
56
|
+
end
|
57
|
+
|
58
|
+
describe :following_by_type_in_method_missing do
|
59
|
+
it { expect(user.following_bands.count).to eq 1 }
|
60
|
+
it { expect(user.following_bands).to be_an_instance_of(ActiveRecord::Relation) }
|
61
|
+
it { expect(user.following_bands.first).to be_an_instance_of(Band) }
|
62
|
+
|
63
|
+
it { expect(user.following_concerts.count).to eq 0 }
|
64
|
+
it { expect(user.following_concerts).to be_an_instance_of(ActiveRecord::Relation) }
|
65
|
+
end
|
66
|
+
|
67
|
+
describe :following_fields_by_type_in_method_missing do
|
68
|
+
it { expect(user.following_band_ids.count).to eq 1 }
|
69
|
+
it { expect(user.following_band_ids.first).to eq band.id }
|
70
|
+
it { expect(user.following_concert_ids.count).to eq 0 }
|
71
|
+
end
|
72
|
+
|
73
|
+
describe :update_follow_counter do
|
74
|
+
it { expect(user.followings_count).to eq 1 }
|
75
|
+
end
|
76
|
+
|
77
|
+
describe :respond_to? do
|
78
|
+
it { expect(user.respond_to?(:following_bands)).to be_true }
|
79
|
+
it { expect(user.respond_to?(:following_band_ids)).to be_true }
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
describe :AliasMethods do
|
84
|
+
subject { User.create }
|
85
|
+
|
86
|
+
it { should respond_to :start_following }
|
87
|
+
it { should respond_to :stop_following }
|
88
|
+
end
|
89
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
$:.unshift File.join(File.dirname(__FILE__), *%w[.. lib])
|
2
|
+
|
3
|
+
require 'rspec'
|
4
|
+
require 'sqlite3'
|
5
|
+
|
6
|
+
require 'partisan'
|
7
|
+
|
8
|
+
# Require our macros and extensions
|
9
|
+
Dir[File.expand_path('../../spec/support/macros/*.rb', __FILE__)].map(&method(:require))
|
10
|
+
Dir[File.expand_path('../../spec/support/extensions/*.rb', __FILE__)].map(&method(:require))
|
11
|
+
|
12
|
+
|
13
|
+
RSpec.configure do |config|
|
14
|
+
# Include our macros
|
15
|
+
config.include DatabaseMacros
|
16
|
+
config.include ModelMacros
|
17
|
+
|
18
|
+
config.before(:each) do
|
19
|
+
# Create the SQLite database
|
20
|
+
setup_database
|
21
|
+
|
22
|
+
# Run our migration
|
23
|
+
run_default_migration
|
24
|
+
end
|
25
|
+
|
26
|
+
config.after(:each) do
|
27
|
+
# Make sure we remove our test database file
|
28
|
+
cleanup_database
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module DatabaseMacros
|
2
|
+
# Run migrations in the test database
|
3
|
+
def run_migration(&block)
|
4
|
+
# Create a new migration class
|
5
|
+
klass = Class.new(ActiveRecord::Migration)
|
6
|
+
|
7
|
+
# Create a new `up` that executes the argument
|
8
|
+
klass.send(:define_method, :up) { self.instance_exec(&block) }
|
9
|
+
|
10
|
+
# Create a new instance of it and execute its `up` method
|
11
|
+
klass.new.up
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.database_file
|
15
|
+
@database_file || File.join(File.dirname(__FILE__), 'test.db')
|
16
|
+
end
|
17
|
+
|
18
|
+
def setup_database
|
19
|
+
# Make sure the test database file is gone
|
20
|
+
cleanup_database
|
21
|
+
|
22
|
+
# Establish the connection
|
23
|
+
SQLite3::Database.new FileUtils.touch(DatabaseMacros.database_file).first
|
24
|
+
ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: DatabaseMacros.database_file)
|
25
|
+
|
26
|
+
# Silence everything
|
27
|
+
ActiveRecord::Base.logger = ActiveRecord::Migration.verbose = false
|
28
|
+
end
|
29
|
+
|
30
|
+
def cleanup_database
|
31
|
+
FileUtils.rm(DatabaseMacros.database_file) if File.exists?(DatabaseMacros.database_file)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Run the built-in migration
|
35
|
+
def run_default_migration
|
36
|
+
load File.join(File.dirname(__FILE__), '../../../lib/generators/partisan/templates/migration.rb')
|
37
|
+
AddFollowsMigration.new.up
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module ModelMacros
|
2
|
+
# Create a new emotional model
|
3
|
+
def followable(klass_name, &block)
|
4
|
+
spawn_model klass_name, ActiveRecord::Base do
|
5
|
+
acts_as_followable
|
6
|
+
instance_exec(&block) if block
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
# Create a new emotive model
|
11
|
+
def follower(klass_name, &block)
|
12
|
+
spawn_model klass_name, ActiveRecord::Base do
|
13
|
+
acts_as_follower
|
14
|
+
class_eval(&block) if block
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
protected
|
19
|
+
|
20
|
+
# Create a new model class
|
21
|
+
def spawn_model(klass_name, parent_klass, &block)
|
22
|
+
Object.instance_eval { remove_const klass_name } if Object.const_defined?(klass_name)
|
23
|
+
Object.const_set(klass_name, Class.new(parent_klass))
|
24
|
+
Object.const_get(klass_name).class_eval(&block) if block_given?
|
25
|
+
end
|
26
|
+
end
|
metadata
ADDED
@@ -0,0 +1,145 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: partisan
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Simon Prévost
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-06-23 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rails
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '>='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 3.0.0
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - '>='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 3.0.0
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ~>
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.3'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ~>
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.3'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - '>='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: sqlite3
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - '>='
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - '>='
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
description: Partisan is a Ruby library that allows ActiveRecord records to be follower
|
84
|
+
and followable, just like on popular social networks. It’s heavily inspired by the
|
85
|
+
origin acts_as_follower which is no longer maintened
|
86
|
+
email:
|
87
|
+
- sprevost@mirego.com
|
88
|
+
executables: []
|
89
|
+
extensions: []
|
90
|
+
extra_rdoc_files: []
|
91
|
+
files:
|
92
|
+
- .gitignore
|
93
|
+
- Gemfile
|
94
|
+
- LICENSE.txt
|
95
|
+
- README.md
|
96
|
+
- Rakefile
|
97
|
+
- lib/generators/partisan/USAGE
|
98
|
+
- lib/generators/partisan/install_generator.rb
|
99
|
+
- lib/generators/partisan/templates/migration.rb
|
100
|
+
- lib/partisan.rb
|
101
|
+
- lib/partisan/follow.rb
|
102
|
+
- lib/partisan/follow_helper.rb
|
103
|
+
- lib/partisan/followable.rb
|
104
|
+
- lib/partisan/follower.rb
|
105
|
+
- lib/partisan/railtie.rb
|
106
|
+
- lib/partisan/version.rb
|
107
|
+
- partisan.gemspec
|
108
|
+
- spec/followable_spec.rb
|
109
|
+
- spec/follower_spec.rb
|
110
|
+
- spec/spec_helper.rb
|
111
|
+
- spec/support/extensions/active_record.rb
|
112
|
+
- spec/support/macros/database_macros.rb
|
113
|
+
- spec/support/macros/model_macros.rb
|
114
|
+
homepage: https://github.com/simonprev/partisan
|
115
|
+
licenses:
|
116
|
+
- BSD 3-Clause
|
117
|
+
metadata: {}
|
118
|
+
post_install_message:
|
119
|
+
rdoc_options: []
|
120
|
+
require_paths:
|
121
|
+
- lib
|
122
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
123
|
+
requirements:
|
124
|
+
- - '>='
|
125
|
+
- !ruby/object:Gem::Version
|
126
|
+
version: '0'
|
127
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - '>='
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
requirements: []
|
133
|
+
rubyforge_project:
|
134
|
+
rubygems_version: 2.0.2
|
135
|
+
signing_key:
|
136
|
+
specification_version: 4
|
137
|
+
summary: Partisan is a Ruby library that allows ActiveRecord records to be follower
|
138
|
+
and followable
|
139
|
+
test_files:
|
140
|
+
- spec/followable_spec.rb
|
141
|
+
- spec/follower_spec.rb
|
142
|
+
- spec/spec_helper.rb
|
143
|
+
- spec/support/extensions/active_record.rb
|
144
|
+
- spec/support/macros/database_macros.rb
|
145
|
+
- spec/support/macros/model_macros.rb
|