subscribable 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,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format documentation
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'activerecord', '3.2.12'
4
+ gem 'rspec', '~> 2.13.0'
5
+ gem 'sqlite3'
6
+ gem 'pry-debugger'
7
+ gem 'activesupport'
8
+
9
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Marius Pop
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,66 @@
1
+ # Subscribable
2
+
3
+ This is a basic gem that allows different models to
4
+ subscribe/follow each other.
5
+
6
+ This gem is supported by American Honors. (http://americanhonor.org)
7
+
8
+ ## Example: A model instance can subscribe to another model instance
9
+
10
+ person = Person.new
11
+ group = Group.new
12
+
13
+ person.subscribe_to!(group)
14
+
15
+ Now your person has a collection of subscriptions that includes
16
+ a group.
17
+
18
+ person.subscriptions #=> [group]
19
+
20
+ What you do with this is up to you. You could create some sort
21
+ of activity feed based on subscriptions.
22
+
23
+ ## Some additional helper methods:
24
+
25
+ person.subscribed_to?(group) #=> true
26
+
27
+ group.person_subscribers #=> [group]
28
+
29
+ person.delete_subscription_to!(group)
30
+
31
+ ## Installation
32
+
33
+ Add this line to your application's Gemfile:
34
+
35
+ gem 'subscribable'
36
+
37
+ And then execute:
38
+
39
+ $ bundle
40
+
41
+ Or install it yourself as:
42
+
43
+ $ gem install subscribable
44
+
45
+ In your terminal run:
46
+
47
+ rails g subscribable:sub_relation
48
+
49
+ Then make sure you run your migration:
50
+
51
+ rake db:migrate
52
+
53
+ In order to make a model subscribable you need
54
+ this code in your model:
55
+
56
+ include Subscribable::RelationAssigner
57
+
58
+ subscribable
59
+
60
+ ## Contributing
61
+
62
+ 1. Fork it
63
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
64
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
65
+ 4. Push to the branch (`git push origin my-new-feature`)
66
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,18 @@
1
+ require 'rails/generators/migration'
2
+
3
+ module Subscribable
4
+ module Generators
5
+ class SubRelationGenerator < Rails::Generators::Base
6
+ include Rails::Generators::Migration
7
+ source_root File.expand_path('../templates', __FILE__)
8
+
9
+ def generate_migration
10
+ migration_template 'sub_relation_migration.rb', 'db/migrate/create_sub_relation.rb'
11
+ end
12
+
13
+ def self.next_migration_number(path)
14
+ @migration_number = Time.now.utc.strftime("%Y%m%d%H%M%S").to_i.to_s
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,15 @@
1
+ class CreateSubRelation < ActiveRecord::Migration
2
+ def change
3
+ create_table :sub_relations do |t|
4
+ t.references :subscription, polymorphic: true
5
+ t.references :subscriber, polymorphic: true
6
+ t.string :status
7
+ t.timestamps
8
+ end
9
+
10
+ add_index :sub_relations,
11
+ [:subscriber_id, :subscriber_type, :subscription_id, :subscription_type],
12
+ unique: true,
13
+ name: 'composite_subscribable_index'
14
+ end
15
+ end
@@ -0,0 +1,94 @@
1
+ require 'active_support/concern'
2
+ require 'active_support'
3
+
4
+ module Subscribable
5
+ module RelationAssigner
6
+ extend ActiveSupport::Concern
7
+
8
+ module ClassMethods
9
+ def subscribable
10
+ has_many :subscribers, class_name: 'Subscribable::SubRelation', as: :subscription
11
+ has_many :subscriptions, class_name: 'Subscribable::SubRelation', as: :subscriber
12
+ end
13
+
14
+ def subscribable?
15
+ true
16
+ end
17
+ end
18
+
19
+ def subscribe_to!(actor)
20
+ sub = subscriptions.new
21
+ sub.subscription = actor
22
+ sub.save
23
+ end
24
+
25
+ def delete_subscription_to!(actor)
26
+ subscription = subscription_with(actor)
27
+ subscription.delete
28
+ end
29
+
30
+ def subscription_status_with(actor)
31
+ subscription = subscription_with(actor)
32
+ subscription.nil? ? 'no relationship' : subscription.status
33
+ end
34
+
35
+ def update_subscription_status!(actor, status)
36
+ subscription = subscription_with(actor)
37
+ subscription.status = status
38
+ subscription.save
39
+ end
40
+
41
+ def subscribed_to?(actor)
42
+ query = actor.subscribers.where(subscriber_id: id, subscriber_type: self.class.name)
43
+ query.empty? ? false : true
44
+ end
45
+
46
+ def respond_to?(method_name, include_all=false)
47
+ class_name = klass_name(method_name)
48
+ method_name.to_s.match('_subscri') ? class_name.constantize.respond_to?(:subscribable?) : super
49
+ end
50
+
51
+ def method_missing(method_name, *args, &block)
52
+ class_name = klass_name(method_name)
53
+ case method_name.to_s
54
+ when /_subscribers/
55
+ subscribers_query(method_name, class_name)
56
+ when /_subscriptions/
57
+ subscriptions_query(method_name, class_name)
58
+ else
59
+ super
60
+ end
61
+ end
62
+
63
+ private
64
+ def klass_name(value)
65
+ value = value.to_s.split('_')
66
+ value.pop
67
+ value.map{|name| name.capitalize}.join
68
+ end
69
+
70
+ def subscriptions_query(method_name, class_name)
71
+ self.class.define_method(method_name) do
72
+ Subscribable::SubRelation.where(
73
+ subscriber_id: self.id,
74
+ subscriber_type: self.class.name,
75
+ subscription_type: class_name )
76
+ end
77
+ self.send(method_name)
78
+ end
79
+
80
+ def subscribers_query(method_name, class_name)
81
+ self.class.define_method(method_name) do
82
+ Subscribable::SubRelation.where(
83
+ subscription_type: self.class.name,
84
+ subscription_id: self.id,
85
+ subscriber_type: class_name )
86
+ end
87
+ self.send(method_name)
88
+ end
89
+
90
+ def subscription_with(actor)
91
+ subscriptions.where(subscription_id: actor.id, subscription_type: actor.class.name).first
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,6 @@
1
+ module Subscribable
2
+ class SubRelation < ActiveRecord::Base
3
+ belongs_to :subscriber, polymorphic: true
4
+ belongs_to :subscription, polymorphic: true
5
+ end
6
+ end
@@ -0,0 +1,3 @@
1
+ module Subscribable
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,2 @@
1
+ require 'subscribable/relation_assigner'
2
+ require 'subscribable/sub_relation'
@@ -0,0 +1,100 @@
1
+ require 'spec_helper'
2
+ require 'fake_models/actor'
3
+
4
+ describe 'Actor' do
5
+ let(:actor) { Actor.create!(name: 'George') }
6
+ let(:star) { Actor.create!(name: 'Mike') }
7
+
8
+ it 'responds to actor_subscribers' do
9
+ expect( actor.respond_to?(:actor_subscribers) ).to be_true
10
+ end
11
+
12
+ it 'responds to actor_subscriptions' do
13
+ expect( actor.respond_to?(:actor_subscriptions) ).to be_true
14
+ end
15
+
16
+ it 'has subscriptions' do
17
+ expect{ actor.subscriptions }.to_not raise_error(NoMethodError)
18
+ end
19
+
20
+ it 'has subscribers' do
21
+ expect{ actor.subscribers }.to_not raise_error(NoMethodError)
22
+ end
23
+
24
+ it 'is subscribable' do
25
+ expect( Actor.subscribable? ).to be_true
26
+ end
27
+
28
+ it 'subscribe(actor) subscribes to an actor' do
29
+ star.subscribe_to!(actor)
30
+ expect(actor.subscribers.size).to eq(1)
31
+ end
32
+
33
+ context 'existance of relationship' do
34
+ it 'subscribed_to?(actor) returns true' do
35
+ sub = star.subscriptions.new
36
+ sub.subscription = actor
37
+ sub.save
38
+ expect(star.subscribed_to?(actor)).to be_true
39
+ end
40
+
41
+ it 'subscribed_to?(actor) returns false' do
42
+ expect(star.subscribed_to?(actor)).to be_false
43
+ end
44
+ end
45
+
46
+ context 'without sub_relations' do
47
+ it 'can query subscribers from subscribable models' do
48
+ expect( actor.actor_subscribers ).to eq([])
49
+ end
50
+
51
+ it 'can query subscriptions from subscribable models' do
52
+ expect( actor.actor_subscriptions ).to eq([])
53
+ end
54
+ end
55
+
56
+ context 'with sub_relations' do
57
+ before do
58
+ sub = actor.subscriptions.new
59
+ sub.subscription = star
60
+ sub.save
61
+ end
62
+ it 'can query subscriptions from subscribable models' do
63
+ expect( actor.actor_subscriptions.size ).to eq(1)
64
+ end
65
+
66
+ it 'can query subscribers from subscribable models' do
67
+ expect( star.actor_subscribers.size ).to eq(1)
68
+ end
69
+ end
70
+
71
+ context 'relationship status' do
72
+ it 'has no relationship' do
73
+ expect( star.subscription_status_with(actor) ).to eq('no relationship')
74
+ end
75
+
76
+ it 'has friendship relationship' do
77
+ sub = star.subscriptions.new
78
+ sub.subscription = actor
79
+ sub.status = 'friendship'
80
+ sub.save
81
+ expect( star.subscription_status_with(actor) ).to eq('friendship')
82
+ end
83
+ end
84
+
85
+ it 'updates subscription status' do
86
+ sub = star.subscriptions.new
87
+ sub.subscription = actor
88
+ sub.status = 'pending'
89
+ sub.save
90
+ star.update_subscription_status!(actor, 'friendship')
91
+ sub = star.subscriptions.where(subscription_id: actor.id, subscription_type: actor.class.name).first
92
+ expect(sub.status).to eq('friendship')
93
+ end
94
+
95
+ it 'delete subscription to actor' do
96
+ star.subscribe_to!(actor)
97
+ star.delete_subscription_to!(actor)
98
+ expect(star.subscribed_to?(actor)).to be_false
99
+ end
100
+ end
@@ -0,0 +1,7 @@
1
+ require 'active_record'
2
+ require 'subscribable'
3
+
4
+ class Actor < ActiveRecord::Base
5
+ include Subscribable::RelationAssigner
6
+ subscribable
7
+ end
@@ -0,0 +1,24 @@
1
+ require 'spec_helper'
2
+ require 'fake_models/actor'
3
+
4
+ describe 'Relation Assigner' do
5
+
6
+ let(:actor) { Actor.create(name: 'Marius') }
7
+ let(:actor2) { Actor.create(name: 'Quad') }
8
+
9
+ it 'allows saving subscribable models' do
10
+ sub = actor.subscriptions.new
11
+ sub.subscriber = actor2
12
+ expect{ sub.save}.to_not raise_error
13
+ end
14
+
15
+ it 'allows saving unique subscribable models only' do
16
+ sub = actor.subscriptions.new
17
+ sub.subscription = actor2
18
+ sub.save
19
+
20
+ sub2 = actor.subscriptions.new
21
+ sub2.subscription = actor2
22
+ expect{ sub2.save }.to raise_error(ActiveRecord::RecordNotUnique)
23
+ end
24
+ end
@@ -0,0 +1,33 @@
1
+ require 'rubygems'
2
+ require 'bundler/setup'
3
+ require 'active_record'
4
+
5
+ RSpec.configure do |config|
6
+ end
7
+
8
+ ActiveRecord::Base.establish_connection(
9
+ :adapter => "sqlite3",
10
+ :database => ":memory:"
11
+ )
12
+
13
+ class CreateActors < ActiveRecord::Migration
14
+ def self.up
15
+ create_table :actors do |t|
16
+ t.string :name
17
+ end
18
+
19
+ create_table :sub_relations do |t|
20
+ t.references :subscription, polymorphic: true
21
+ t.references :subscriber, polymorphic: true
22
+ t.string :status
23
+ t.timestamps
24
+ end
25
+
26
+ add_index :sub_relations,
27
+ [:subscriber_id, :subscriber_type, :subscription_id, :subscription_type],
28
+ unique: true,
29
+ name: 'composite_subscribable_index'
30
+ end
31
+ end
32
+
33
+ catch_output = CreateActors.up
@@ -0,0 +1,25 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'subscribable/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "subscribable"
8
+ spec.version = Subscribable::VERSION
9
+ spec.authors = ["Marius Pop"]
10
+ spec.email = ["marius@mlpinit.com"]
11
+ spec.description = %q{ Groups, Users, or other models can subscribe to each other.}
12
+ spec.summary = %q{ A way models can subscribe to each other.}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
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{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.3"
22
+ spec.add_development_dependency "rake"
23
+
24
+ spec.add_dependency 'activerecord'
25
+ end
metadata ADDED
@@ -0,0 +1,116 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: subscribable
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Marius Pop
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-03-15 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: bundler
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '1.3'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '1.3'
30
+ - !ruby/object:Gem::Dependency
31
+ name: rake
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: activerecord
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ description: ! ' Groups, Users, or other models can subscribe to each other.'
63
+ email:
64
+ - marius@mlpinit.com
65
+ executables: []
66
+ extensions: []
67
+ extra_rdoc_files: []
68
+ files:
69
+ - .gitignore
70
+ - .rspec
71
+ - Gemfile
72
+ - LICENSE.txt
73
+ - README.md
74
+ - Rakefile
75
+ - lib/generators/subscribable/sub_relation_generator.rb
76
+ - lib/generators/subscribable/templates/sub_relation_migration.rb
77
+ - lib/subscribable.rb
78
+ - lib/subscribable/relation_assigner.rb
79
+ - lib/subscribable/sub_relation.rb
80
+ - lib/subscribable/version.rb
81
+ - spec/actor_spec.rb
82
+ - spec/fake_models/actor.rb
83
+ - spec/relation_assigner_spec.rb
84
+ - spec/spec_helper.rb
85
+ - subscribable.gemspec
86
+ homepage: ''
87
+ licenses:
88
+ - MIT
89
+ post_install_message:
90
+ rdoc_options: []
91
+ require_paths:
92
+ - lib
93
+ required_ruby_version: !ruby/object:Gem::Requirement
94
+ none: false
95
+ requirements:
96
+ - - ! '>='
97
+ - !ruby/object:Gem::Version
98
+ version: '0'
99
+ required_rubygems_version: !ruby/object:Gem::Requirement
100
+ none: false
101
+ requirements:
102
+ - - ! '>='
103
+ - !ruby/object:Gem::Version
104
+ version: '0'
105
+ requirements: []
106
+ rubyforge_project:
107
+ rubygems_version: 1.8.25
108
+ signing_key:
109
+ specification_version: 3
110
+ summary: A way models can subscribe to each other.
111
+ test_files:
112
+ - spec/actor_spec.rb
113
+ - spec/fake_models/actor.rb
114
+ - spec/relation_assigner_spec.rb
115
+ - spec/spec_helper.rb
116
+ has_rdoc: