Browse files

Refactored to support multiple data stores

  • Loading branch information...
1 parent 7e81a4d commit ca47151ac3deb617e7093b3973b0c0fc32cbe96e @cmer committed Jun 5, 2012
Showing with 1,551 additions and 1,027 deletions.
  1. +41 −0 CHANGELOG.md
  2. +3 −0 README.md
  3. +0 −1 generators/socialization/USAGE
  4. +0 −15 generators/socialization/socialization_generator.rb
  5. +0 −3 generators/socialization/templates/model_follow.rb
  6. +0 −3 generators/socialization/templates/model_like.rb
  7. +0 −3 generators/socialization/templates/model_mention.rb
  8. +20 −9 lib/generators/socialization/socialization_generator.rb
  9. 0 ...ialization/templates → lib/generators/socialization/templates/active_record}/migration_follows.rb
  10. 0 ...ocialization/templates → lib/generators/socialization/templates/active_record}/migration_likes.rb
  11. 0 ...alization/templates → lib/generators/socialization/templates/active_record}/migration_mentions.rb
  12. +2 −0 lib/generators/socialization/templates/active_record/model_follow.rb
  13. +2 −0 lib/generators/socialization/templates/active_record/model_like.rb
  14. +2 −0 lib/generators/socialization/templates/active_record/model_mention.rb
  15. +74 −0 lib/socialization/actors/follower.rb
  16. +74 −0 lib/socialization/actors/liker.rb
  17. +74 −0 lib/socialization/actors/mentioner.rb
  18. +8 −45 lib/socialization/acts_as_helpers.rb
  19. +0 −9 lib/socialization/follow_store.rb
  20. +0 −44 lib/socialization/followable.rb
  21. +0 −87 lib/socialization/follower.rb
  22. +13 −0 lib/socialization/helpers/string.rb
  23. +0 −9 lib/socialization/like_store.rb
  24. +0 −44 lib/socialization/likeable.rb
  25. +0 −90 lib/socialization/liker.rb
  26. +0 −9 lib/socialization/mention_store.rb
  27. +0 −46 lib/socialization/mentionable.rb
  28. +0 −90 lib/socialization/mentioner.rb
  29. +143 −0 lib/socialization/stores/active_record/follow_store.rb
  30. +143 −0 lib/socialization/stores/active_record/like_store.rb
  31. +143 −0 lib/socialization/stores/active_record/mention_store.rb
  32. +38 −0 lib/socialization/victims/followable.rb
  33. +38 −0 lib/socialization/victims/likeable.rb
  34. +38 −0 lib/socialization/victims/mentionable.rb
  35. +74 −0 test/actors/follower_test.rb
  36. +74 −0 test/actors/liker_test.rb
  37. +74 −0 test/actors/mentioner_test.rb
  38. +0 −166 test/follow_test.rb
  39. +0 −165 test/like_test.rb
  40. +0 −170 test/mention_test.rb
  41. +117 −0 test/stores/active_record/follow_store_test.rb
  42. +117 −0 test/stores/active_record/like_store_test.rb
  43. +117 −0 test/stores/active_record/mention_store_test.rb
  44. +13 −0 test/string_test.rb
  45. +4 −12 test/test_helper.rb
  46. +34 −0 test/victims/followable_test.rb
  47. +34 −0 test/victims/likeable_test.rb
  48. +34 −0 test/victims/mentionable_test.rb
  49. +3 −7 test/world_test.rb
View
41 CHANGELOG.md
@@ -0,0 +1,41 @@
+# Changelog
+
+## Master
+
+* **MAJOR REFACTORING:** Your Like, Follow and Mention models should now inherit the Socialization store base class instead of using the acts_as helper. (e.g.: class Follow < Socialization::ActiveRecordStores::FollowStore). See demo app for an example. Your code should be mostly unaffected.
+* Changed: The persistence logic has now been moved to the Socialization::ActiveRecordStores namespace. More stores such as Redis or MongoDB can be easily added.
+* Changed: `like!`, `follow!`, and `mention!` now return a boolean. True when the action was successful, false when it wasn't (e.g.: the relationship already exists).
+* Changed: `unlike!`, `unfollow!` and `unmention!` will now return false if there is no record to destroy rather than raising `ActiveRecord::RecordNotFound`.
+* Changed: Records can now like, follow or mention themselves. If you want to prevent this, it should be enforced directly in your application.
+* Added: `toggle_like!`, `toggle_follow!` and `toggle_mention!` methods. Thanks to [@balvig](https://github.com/balvig).
+* Added: support for single table inheritance. Thanks to [@balvig](https://github.com/balvig).
+
+## v0.4.0 (February 25, 2012)
+
+* **BREAKING CHANGE:** Renamed `mentionner` to `mentioner`. This is proper English.
+* Added: `followees`, `likees` and `mentionees` methods to `Follower`, `Liker` and `Mentioner`. Thanks to [@ihara2525](https://github.com/ihara2525).
+
+## v0.3.0 (February 22, 2012)
+
+* **BREAKING CHANGE:** `likers`, `followers` now return a scope instead of an array. They also require to have the class of the desired scope as an argument. For example: `Movie.find(1).followers(User)`.
+* Added: Mention support.
+* Some refactoring and clean up. Thanks to [@tilsammans](https://github.com/tilsammans)
+
+
+## 0.2.2 (January 15, 2012)
+
+* Improved tests.
+* Changed: Can no longer like or follow yourself.
+
+## 0.2.1 (January 15, 2012)
+
+* Bug fixes
+
+## 0.2.0 (January 15, 2012)
+
+* Bug fixes
+* Made Ruby 1.8.7 compatible
+
+## 0.1.0 (January 14, 2012)
+
+* Initial release
View
3 README.md
@@ -209,6 +209,9 @@ You can find the compiled YARD documentation at http://rubydoc.info/github/cmer/
***
+## Changelog
+
+Here it is: https://github.com/cmer/socialization/blob/master/CHANGELOG.md
## Demo App
View
1 generators/socialization/USAGE
@@ -1 +0,0 @@
-Generates the Follow, Like and Mention models as well as their migrations.
View
15 generators/socialization/socialization_generator.rb
@@ -1,15 +0,0 @@
-class SocializationGenerator < Rails::Generator::Base
- def manifest
- record do |m|
- m.template 'model_follow.rb', 'app/models/follow.rb'
- m.template 'model_like.rb', 'app/models/like.rb'
- m.template 'model_mention.rb', 'app/models/mention.rb'
-
- m.migration_template 'migration_follows.rb', 'db/migrate', :migration_file_name => 'create_follows'
- sleep 1 # force unique migration timestamp
- m.migration_template 'migration_likes.rb', 'db/migrate', :migration_file_name => 'create_likes'
- sleep 1 # force unique migration timestamp
- m.migration_template 'migration_mentions.rb', 'db/migrate', :migration_file_name => 'create_mentions'
- end
- end
-end
View
3 generators/socialization/templates/model_follow.rb
@@ -1,3 +0,0 @@
-class Follow < ActiveRecord::Base
- acts_as_follow_store
-end
View
3 generators/socialization/templates/model_like.rb
@@ -1,3 +0,0 @@
-class Like < ActiveRecord::Base
- acts_as_like_store
-end
View
3 generators/socialization/templates/model_mention.rb
@@ -1,3 +0,0 @@
-class Mention < ActiveRecord::Base
- acts_as_mention_store
-end
View
29 lib/generators/socialization/socialization_generator.rb
@@ -1,9 +1,12 @@
require 'rails/generators'
require 'rails/generators/migration'
+STORES = %w(active_record)
+
class SocializationGenerator < Rails::Generators::Base
include Rails::Generators::Migration
- source_root File.expand_path(File.join('..', '..', '..', 'generators', 'socialization', 'templates'), File.dirname(__FILE__))
+ source_root File.expand_path(File.join('templates'), File.dirname(__FILE__))
+ class_option :store, :type => :string, :default => 'active_record', :description => "Type of datastore"
def self.next_migration_number(dirname)
if ActiveRecord::Base.timestamped_migrations
@@ -14,14 +17,22 @@ def self.next_migration_number(dirname)
end
def create_migration_file
- copy_file 'model_follow.rb', 'app/models/follow.rb'
- copy_file 'model_like.rb', 'app/models/like.rb'
- copy_file 'model_mention.rb', 'app/models/mention.rb'
+ options[:store].downcase!
+ unless STORES.include?(options[:store])
+ puts "Invalid store '#{options[:store]}'. The following stores are valid: #{STORES.join(', ')}."
+ exit 1
+ end
- migration_template 'migration_follows.rb', 'db/migrate/create_follows.rb'
- sleep 1 # wait a second to have a unique migration timestamp
- migration_template 'migration_likes.rb', 'db/migrate/create_likes.rb'
- sleep 1 # wait a second to have a unique migration timestamp
- migration_template 'migration_mentions.rb', 'db/migrate/create_mentions.rb'
+ copy_file "#{options[:store]}/model_follow.rb", 'app/models/follow.rb'
+ copy_file "#{options[:store]}/model_like.rb", 'app/models/like.rb'
+ copy_file "#{options[:store]}/model_mention.rb", 'app/models/mention.rb'
+
+ if options[:store] == 'active_record'
+ migration_template "#{options[:store]}/migration_follows.rb", 'db/migrate/create_follows.rb'
+ sleep 1 # wait a second to have a unique migration timestamp
+ migration_template "#{options[:store]}/migration_likes.rb", 'db/migrate/create_likes.rb'
+ sleep 1 # wait a second to have a unique migration timestamp
+ migration_template "#{options[:store]}/migration_mentions.rb", 'db/migrate/create_mentions.rb'
+ end
end
end
View
0 ...ialization/templates/migration_follows.rb → ...plates/active_record/migration_follows.rb
File renamed without changes.
View
0 ...ocialization/templates/migration_likes.rb → ...emplates/active_record/migration_likes.rb
File renamed without changes.
View
0 ...alization/templates/migration_mentions.rb → ...lates/active_record/migration_mentions.rb
File renamed without changes.
View
2 lib/generators/socialization/templates/active_record/model_follow.rb
@@ -0,0 +1,2 @@
+class Follow < Socialization::ActiveRecordStores::FollowStore
+end
View
2 lib/generators/socialization/templates/active_record/model_like.rb
@@ -0,0 +1,2 @@
+class Like < Socialization::ActiveRecordStores::LikeStore
+end
View
2 lib/generators/socialization/templates/active_record/model_mention.rb
@@ -0,0 +1,2 @@
+class Mention < Socialization::ActiveRecordStores::MentionStore
+end
View
74 lib/socialization/actors/follower.rb
@@ -0,0 +1,74 @@
+module ActiveRecord
+ class Base
+ def is_follower?
+ false
+ end
+ end
+end
+
+module Socialization
+ module Follower
+ extend ActiveSupport::Concern
+
+ included do
+ # Specifies if self can follow {Followable} objects.
+ #
+ # @return [Boolean]
+ def is_follower?
+ true
+ end
+
+ # Create a new {FollowStores follow} relationship.
+ #
+ # @param [Followable] followable the object to be followed.
+ # @return [Boolean]
+ def follow!(followable)
+ raise ArgumentError, "#{followable} is not followable!" unless followable.respond_to?(:is_followable?) && followable.is_followable?
+ Follow.follow!(self, followable)
+ end
+
+ # Delete a {FollowStores follow} relationship.
+ #
+ # @param [Followable] followable the object to unfollow.
+ # @return [Boolean]
+ def unfollow!(followable)
+ raise ArgumentError, "#{followable} is not followable!" unless followable.respond_to?(:is_followable?) && followable.is_followable?
+ Follow.unfollow!(self, followable)
+ end
+
+ # Toggles a {FollowStores follow} relationship.
+ #
+ # @param [Followable] followable the object to follow/unfollow.
+ # @return [Boolean]
+ def toggle_follow!(followable)
+ raise ArgumentError, "#{followable} is not followable!" unless followable.respond_to?(:is_followable?) && followable.is_followable?
+ if follows?(followable)
+ unfollow!(followable)
+ false
+ else
+ follow!(followable)
+ true
+ end
+ end
+
+ # Specifies if self follows a {Followable} object.
+ #
+ # @param [Followable] followable the {Followable} object to test against.
+ # @return [Boolean]
+ def follows?(followable)
+ raise ArgumentError, "#{followable} is not followable!" unless followable.respond_to?(:is_followable?) && followable.is_followable?
+ Follow.follows?(self, followable)
+ end
+
+ # Returns all the followables of a certain type that are followed by self
+ #
+ # @params [Followable] klass the type of {Followable} you want
+ # @params [Hash] opts a hash of options
+ # @return [Array<Followable, Numeric>] An array of Followable objects or IDs
+ def followables(klass, opts = {})
+ Follow.followables(self, klass, opts)
+ end
+
+ end
+ end
+end
View
74 lib/socialization/actors/liker.rb
@@ -0,0 +1,74 @@
+module ActiveRecord
+ class Base
+ def is_liker?
+ false
+ end
+ end
+end
+
+module Socialization
+ module Liker
+ extend ActiveSupport::Concern
+
+ included do
+ # Specifies if self can like {Likeable} objects.
+ #
+ # @return [Boolean]
+ def is_liker?
+ true
+ end
+
+ # Create a new {LikeStores like} relationship.
+ #
+ # @param [Likeable] likeable the object to be liked.
+ # @return [Boolean]
+ def like!(likeable)
+ raise ArgumentError, "#{likeable} is not likeable!" unless likeable.respond_to?(:is_likeable?) && likeable.is_likeable?
+ Like.like!(self, likeable)
+ end
+
+ # Delete a {LikeStores like} relationship.
+ #
+ # @param [Likeable] likeable the object to unlike.
+ # @return [Boolean]
+ def unlike!(likeable)
+ raise ArgumentError, "#{likeable} is not likeable!" unless likeable.respond_to?(:is_likeable?) && likeable.is_likeable?
+ Like.unlike!(self, likeable)
+ end
+
+ # Toggles a {LikeStores like} relationship.
+ #
+ # @param [Likeable] likeable the object to like/unlike.
+ # @return [Boolean]
+ def toggle_like!(likeable)
+ raise ArgumentError, "#{likeable} is not likeable!" unless likeable.respond_to?(:is_likeable?) && likeable.is_likeable?
+ if likes?(likeable)
+ unlike!(likeable)
+ false
+ else
+ like!(likeable)
+ true
+ end
+ end
+
+ # Specifies if self likes a {Likeable} object.
+ #
+ # @param [Likeable] likeable the {Likeable} object to test against.
+ # @return [Boolean]
+ def likes?(likeable)
+ raise ArgumentError, "#{likeable} is not likeable!" unless likeable.respond_to?(:is_likeable?) && likeable.is_likeable?
+ Like.likes?(self, likeable)
+ end
+
+ # Returns all the likeables of a certain type that are liked by self
+ #
+ # @params [Likeable] klass the type of {Likeable} you want
+ # @params [Hash] opts a hash of options
+ # @return [Array<Likeable, Numeric>] An array of Likeable objects or IDs
+ def likeables(klass, opts = {})
+ Like.likeables(self, klass, opts)
+ end
+
+ end
+ end
+end
View
74 lib/socialization/actors/mentioner.rb
@@ -0,0 +1,74 @@
+module ActiveRecord
+ class Base
+ def is_mentioner?
+ false
+ end
+ end
+end
+
+module Socialization
+ module Mentioner
+ extend ActiveSupport::Concern
+
+ included do
+ # Specifies if self can mention {Mentionable} objects.
+ #
+ # @return [Boolean]
+ def is_mentioner?
+ true
+ end
+
+ # Create a new {MentionStores mention} relationship.
+ #
+ # @param [Mentionable] mentionable the object to be mentioned.
+ # @return [Boolean]
+ def mention!(mentionable)
+ raise ArgumentError, "#{mentionable} is not mentionable!" unless mentionable.respond_to?(:is_mentionable?) && mentionable.is_mentionable?
+ Mention.mention!(self, mentionable)
+ end
+
+ # Delete a {MentionStores mention} relationship.
+ #
+ # @param [Mentionable] mentionable the object to unmention.
+ # @return [Boolean]
+ def unmention!(mentionable)
+ raise ArgumentError, "#{mentionable} is not mentionable!" unless mentionable.respond_to?(:is_mentionable?) && mentionable.is_mentionable?
+ Mention.unmention!(self, mentionable)
+ end
+
+ # Toggles a {MentionStores mention} relationship.
+ #
+ # @param [Mentionable] mentionable the object to mention/unmention.
+ # @return [Boolean]
+ def toggle_mention!(mentionable)
+ raise ArgumentError, "#{mentionable} is not mentionable!" unless mentionable.respond_to?(:is_mentionable?) && mentionable.is_mentionable?
+ if mentions?(mentionable)
+ unmention!(mentionable)
+ false
+ else
+ mention!(mentionable)
+ true
+ end
+ end
+
+ # Specifies if self mentions a {Mentionable} object.
+ #
+ # @param [Mentionable] mentionable the {Mentionable} object to test against.
+ # @return [Boolean]
+ def mentions?(mentionable)
+ raise ArgumentError, "#{mentionable} is not mentionable!" unless mentionable.respond_to?(:is_mentionable?) && mentionable.is_mentionable?
+ Mention.mentions?(self, mentionable)
+ end
+
+ # Returns all the mentionables of a certain type that are mentioned by self
+ #
+ # @params [Mentionable] klass the type of {Mentionable} you want
+ # @params [Hash] opts a hash of options
+ # @return [Array<Mentionable, Numeric>] An array of Mentionable objects or IDs
+ def mentionables(klass, opts = {})
+ Mention.mentionables(self, klass, opts)
+ end
+
+ end
+ end
+end
View
53 lib/socialization/acts_as_helpers.rb
@@ -1,7 +1,7 @@
require 'active_support/concern'
-%w{followable follower follow_store likeable liker like_store mentionable mentioner mention_store}.each do |f|
- require "#{File.dirname(__FILE__)}/#{f}"
+%w(helpers stores actors victims).each do |path|
+ Dir["#{File.dirname(__FILE__)}/#{path}/**/*.rb"].each { |f| require(f) }
end
module Socialization
@@ -10,71 +10,34 @@ module ActsAsHelpers
module ClassMethods
# Make the current class a {Socialization::Follower}
- def acts_as_follower(opts = nil)
+ def acts_as_follower(opts = {})
include Socialization::Follower
end
# Make the current class a {Socialization::Followable}
- def acts_as_followable(opts = nil)
+ def acts_as_followable(opts = {})
include Socialization::Followable
end
- # Make the current class a {Socialization::FollowStore}
- # @param [Hash] opts the options to create a message with.
- # @option opts [Boolean] :touch_follower :touch value for belongs_to :follower association
- # @option opts [Boolean] :touch_followable :touch value for belongs_to :followable association
- def acts_as_follow_store(opts = nil)
- opts ||= {}
- belongs_to :follower, :polymorphic => true, :touch => opts[:touch_follower] || false
- belongs_to :followable, :polymorphic => true, :touch => opts[:touch_followable] || false
- validates_uniqueness_of :followable_type, :scope => [:followable_id, :follower_type, :follower_id], :message => 'You cannot follow the same thing twice.'
- include Socialization::FollowStore
- end
-
# Make the current class a {Socialization::Liker}
- def acts_as_liker(opts = nil)
+ def acts_as_liker(opts = {})
include Socialization::Liker
end
# Make the current class a {Socialization::Likeable}
- def acts_as_likeable(opts = nil)
+ def acts_as_likeable(opts = {})
include Socialization::Likeable
end
- # Make the current class a {Socialization::LikeStore}
- # @param [Hash] opts the options to create a message with.
- # @option opts [Boolean] :touch_liker :touch value for belongs_to :liker association
- # @option opts [Boolean] :touch_likeable :touch value for belongs_to :likeable association
- def acts_as_like_store(opts = nil)
- opts ||= {}
- belongs_to :liker, :polymorphic => true, :touch => opts[:touch_liker] || false
- belongs_to :likeable, :polymorphic => true, :touch => opts[:touch_likeable] || false
- validates_uniqueness_of :likeable_type, :scope => [:likeable_id, :liker_type, :liker_id], :message => 'You cannot like the same thing twice.'
- include Socialization::LikeStore
- end
-
# Make the current class a {Socialization::Mentioner}
- def acts_as_mentioner(opts = nil)
+ def acts_as_mentioner(opts = {})
include Socialization::Mentioner
end
# Make the current class a {Socialization::Mentionable}
- def acts_as_mentionable(opts = nil)
+ def acts_as_mentionable(opts = {})
include Socialization::Mentionable
end
-
- # Make the current class a {Socialization::MentionStore}
- # @param [Hash] opts the options to create a message with.
- # @option opts [Boolean] :touch_mentioner :touch value for belongs_to :mentioner association
- # @option opts [Boolean] :touch_mentionable :touch value for belongs_to :mentionable association
- def acts_as_mention_store(opts = nil)
- opts ||= {}
- belongs_to :mentioner, :polymorphic => true, :touch => opts[:touch_mentioner] || false
- belongs_to :mentionable, :polymorphic => true, :touch => opts[:touch_mentionable] || false
- validates_uniqueness_of :mentionable_type, :scope => [:mentionable_id, :mentioner_type, :mentioner_id], :message => 'You cannot mention the same thing twice in a given object.'
- include Socialization::MentionStore
- end
-
end
end
end
View
9 lib/socialization/follow_store.rb
@@ -1,9 +0,0 @@
-module Socialization
- module FollowStore
- extend ActiveSupport::Concern
-
- included do
- def self.human_attribute_name(*args); ''; end
- end
- end
-end
View
44 lib/socialization/followable.rb
@@ -1,44 +0,0 @@
-module ActiveRecord
- class Base
- def is_followable?
- false
- end
- end
-end
-
-module Socialization
- module Followable
- extend ActiveSupport::Concern
-
- included do
- # A following is the {FollowStore follow} record of the follower following self.
- has_many :followings, :as => :followable, :dependent => :destroy, :class_name => 'Follow'
-
- # Specifies if self can be followed.
- #
- # @return [Boolean]
- def is_followable?
- true
- end
-
- # Specifies if self is followed by a {Follower} object.
- #
- # @return [Boolean]
- def followed_by?(follower)
- raise ArgumentError, "#{follower} is not a follower!" unless follower.is_follower?
- !self.followings.where(:follower_type => follower.class.to_s, :follower_id => follower.id).empty?
- end
-
- # Returns a scope of the {Follower}s following self.
- #
- # @param [Class] klass the {Follower} class to be included in the scope. e.g. `User`.
- # @return [ActiveRecord::Relation]
- def followers(klass)
- klass = klass.to_s.singularize.camelize.constantize unless klass.is_a?(Class)
- klass.joins("INNER JOIN follows ON follows.follower_id = #{klass.to_s.tableize}.id AND follows.follower_type = '#{klass.to_s}'").
- where("follows.followable_type = '#{self.class.to_s}'").
- where("follows.followable_id = #{self.id}")
- end
- end
- end
-end
View
87 lib/socialization/follower.rb
@@ -1,87 +0,0 @@
-module ActiveRecord
- class Base
- def is_follower?
- false
- end
- end
-end
-
-module Socialization
- module Follower
- extend ActiveSupport::Concern
-
- included do
- # A follow is the Follow record of self following a followable record.
- has_many :follows, :as => :follower, :dependent => :destroy, :class_name => 'Follow'
-
- # Specifies if self can follow {Followable} objects.
- #
- # @return [Boolean]
- def is_follower?
- true
- end
-
- # Create a new {FollowStore follow} relationship.
- #
- # @param [Followable] followable the object to be followed.
- # @return [FollowStore] the newly created {FollowStore follow} record.
- def follow!(followable)
- raise ArgumentError, "#{followable} is not followable!" unless followable.is_followable?
- raise ArgumentError, "#{self} cannot follow itself!" unless self != followable
- Follow.create! do |follow|
- follow.follower = self
- follow.followable = followable
- end
- end
-
- # Delete a {FollowStore follow} relationship.
- #
- # @param [Followable] followable the object to unfollow.
- # @return [Boolean]
- def unfollow!(followable)
- ff = followable.followings.where(:follower_type => self.class.to_s, :follower_id => self.id)
- unless ff.empty?
- ff.each { |f| f.destroy }
- else
- raise ActiveRecord::RecordNotFound
- end
- true
- end
-
- # Toggles a {FollowStore follow} relationship.
- #
- # @param [Followable] followable the object to follow/unfollow.
- # @return [Boolean]
- def toggle_follow!(followable)
- if follows?(followable)
- unfollow!(followable)
- false
- else
- follow!(followable)
- true
- end
- end
-
- # Specifies if self follows a {Followable} object.
- #
- # @param [Followable] followable the {Followable} object to test against.
- # @return [Boolean]
- def follows?(followable)
- raise ArgumentError, "#{followable} is not followable!" unless followable.is_followable?
- !self.follows.where(:followable_type => followable.class.table_name.classify, :followable_id => followable.id).empty?
- end
-
- # Returns a scope of the {Followable}s followed by self.
- #
- # @param [Class] klass the {Followable} class to be included in the scope. e.g. `User`.
- # @return [ActiveRecord::Relation]
- def followees(klass)
- klass = klass.to_s.singularize.camelize.constantize unless klass.is_a?(Class)
- klass.joins("INNER JOIN follows ON follows.followable_id = #{klass.to_s.tableize}.id AND follows.followable_type = '#{klass.to_s}'").
- where("follows.follower_type = '#{self.class.to_s}'").
- where("follows.follower_id = #{self.id}")
-
- end
- end
- end
-end
View
13 lib/socialization/helpers/string.rb
@@ -0,0 +1,13 @@
+class String
+
+ def deep_const_get
+ result = nil
+ path = self.clone.split("::")
+
+ path.each do |p|
+ result = (result || Kernel).const_get(p)
+ end
+ result
+ end
+
+end
View
9 lib/socialization/like_store.rb
@@ -1,9 +0,0 @@
-module Socialization
- module LikeStore
- extend ActiveSupport::Concern
-
- included do
- def self.human_attribute_name(*args); ''; end
- end
- end
-end
View
44 lib/socialization/likeable.rb
@@ -1,44 +0,0 @@
-module ActiveRecord
- class Base
- def is_likeable?
- false
- end
- end
-end
-
-module Socialization
- module Likeable
- extend ActiveSupport::Concern
-
- included do
- # A liking is the {Like} record of the liker liking self.
- has_many :likings, :as => :likeable, :dependent => :destroy, :class_name => 'Like'
-
- # Specifies if self can be liked.
- #
- # @return [Boolean]
- def is_likeable?
- true
- end
-
- # Specifies if self is liked by a {Liker} object.
- #
- # @return [Boolean]
- def liked_by?(liker)
- raise ArgumentError, "#{liker} is not a liker!" unless liker.is_liker?
- !self.likings.where(:liker_type => liker.class.to_s, :liker_id => liker.id).empty?
- end
-
- # Returns a scope of the {Liker}s liking self.
- #
- # @param [Class] klass the {Liker} class to be included in the scope. e.g. `User`.
- # @return [ActiveRecord::Relation]
- def likers(klass)
- klass = klass.to_s.singularize.camelize.constantize unless klass.is_a?(Class)
- klass.joins("INNER JOIN likes ON likes.liker_id = #{klass.to_s.tableize}.id AND likes.liker_type = '#{klass.to_s}'").
- where("likes.likeable_type = '#{self.class.to_s}'").
- where("likes.likeable_id = #{self.id}")
- end
- end
- end
-end
View
90 lib/socialization/liker.rb
@@ -1,90 +0,0 @@
-module ActiveRecord
- class Base
- def is_liker?
- false
- end
- end
-end
-
-module Socialization
- module Liker
- extend ActiveSupport::Concern
-
- included do
- # A like is the Like record of self liking a likeable record.
- has_many :likes, :as => :liker, :dependent => :destroy, :class_name => 'Like'
-
- # Specifies if self can like {Likeable} objects.
- #
- # @return [Boolean]
- def is_liker?
- true
- end
-
- # Create a new {LikeStore like} relationship.
- #
- # @param [Likeable] likeable the object to be liked.
- # @return [LikeStore] the newly created {LikeStore like} record.
- def like!(likeable)
- ensure_likeable!(likeable)
- raise ArgumentError, "#{self} cannot like itself!" unless self != likeable
- Like.create! do |like|
- like.liker = self
- like.likeable = likeable
- end
- end
-
- # Delete a {LikeStore like} relationship.
- #
- # @param [Likeable] likeable the object to unlike.
- # @return [Boolean]
- def unlike!(likeable)
- ll = likeable.likings.where(:liker_type => self.class.to_s, :liker_id => self.id)
- unless ll.empty?
- ll.each { |l| l.destroy }
- else
- raise ActiveRecord::RecordNotFound
- end
- end
-
- # Toggles a {LikeStore like} relationship.
- #
- # @param [Likeable] likeable the object to like/unlike.
- # @return [Boolean]
- def toggle_like!(likeable)
- if likes?(likeable)
- unlike!(likeable)
- false
- else
- like!(likeable)
- true
- end
- end
-
- # Specifies if self likes a {Likeable} object.
- #
- # @param [Likeable] likeable the {Likeable} object to test against.
- # @return [Boolean]
- def likes?(likeable)
- ensure_likeable!(likeable)
- !self.likes.where(:likeable_type => likeable.class.table_name.classify, :likeable_id => likeable.id).empty?
- end
-
- # Returns a scope of the {Likeable}s followed by self.
- #
- # @param [Class] klass the {Likeable} class to be included in the scope. e.g. `Movie`.
- # @return [ActiveRecord::Relation]
- def likees(klass)
- klass = klass.to_s.singularize.camelize.constantize unless klass.is_a?(Class)
- klass.joins("INNER JOIN likes ON likes.likeable_id = #{klass.to_s.tableize}.id AND likes.likeable_type = '#{klass.to_s}'").
- where("likes.liker_type = '#{self.class.to_s}'").
- where("likes.liker_id = #{self.id}")
- end
-
- private
- def ensure_likeable!(likeable)
- raise ArgumentError, "#{likeable} is not likeable!" unless likeable.is_likeable?
- end
- end
- end
-end
View
9 lib/socialization/mention_store.rb
@@ -1,9 +0,0 @@
-module Socialization
- module MentionStore
- extend ActiveSupport::Concern
-
- included do
- def self.human_attribute_name(*args); ''; end
- end
- end
-end
View
46 lib/socialization/mentionable.rb
@@ -1,46 +0,0 @@
-module ActiveRecord
- class Base
- def is_mentionable?
- false
- end
- end
-end
-
-module Socialization
- module Mentionable
- extend ActiveSupport::Concern
-
- included do
- # A mentioning is the Mention record of describing the mention relationship between
- # the mentioner and the mentionable (self).
- has_many :mentionings, :as => :mentionable, :dependent => :destroy, :class_name => 'Mention'
-
- # Specifies if self can be mentioned.
- #
- # @return [Boolean]
- def is_mentionable?
- true
- end
-
- # Specifies if self is mentioned by a {Mentioner} object.
- #
- # @return [Boolean]
- def mentioned_by?(mentioner)
- raise ArgumentError, "#{mentioner} is not a mentioner!" unless mentioner.is_mentioner?
- !self.mentionings.where(:mentioner_type => mentioner.class.to_s, :mentioner_id => mentioner.id).empty?
- end
-
- # Returns a scope of the {Mentioner}s mentioning self.
- #
- # @param [Class] klass the {Mentioner} class to be included in the scope. e.g. `Comment`.
- # @return [ActiveRecord::Relation]
- def mentioners(klass)
- klass = klass.to_s.singularize.camelize.constantize unless klass.is_a?(Class)
- klass.joins("INNER JOIN mentions ON mentions.mentioner_id = #{klass.to_s.tableize}.id AND mentions.mentioner_type = '#{klass.to_s}'").
- where("mentions.mentionable_type = '#{self.class.to_s}'").
- where("mentions.mentionable_id = #{self.id}")
- end
-
- end
- end
-end
View
90 lib/socialization/mentioner.rb
@@ -1,90 +0,0 @@
-module ActiveRecord
- class Base
- def is_mentioner?
- false
- end
- end
-end
-
-module Socialization
- module Mentioner
- extend ActiveSupport::Concern
-
- included do
- # A mention is the Mention record (self) mentionning a mentionable record.
- has_many :mentions, :as => :mentioner, :dependent => :destroy, :class_name => 'Mention'
-
- # Specifies if self can mention {Mentionable} objects.
- #
- # @return [Boolean]
- def is_mentioner?
- true
- end
-
- # Create a new {MentionStore mention} relationship.
- #
- # @param [Mentionable] mentionable the object to be mentioned.
- # @return [MentionStore] the newly created {MentionStore mention} record.
- def mention!(mentionable)
- ensure_mentionable!(mentionable)
- Mention.create! do |mention|
- mention.mentioner = self
- mention.mentionable = mentionable
- end
- end
-
- # Delete a {MentionStore mention} relationship.
- #
- # @param [Mentionable] mentionable the object to unmention.
- # @return [Boolean]
- def unmention!(mentionable)
- mm = mentionable.mentionings.where(:mentioner_type => self.class.to_s, :mentioner_id => self.id)
- unless mm.empty?
- mm.each { |m| m.destroy }
- else
- raise ActiveRecord::RecordNotFound
- end
- end
- #
- # Toggles a {MentionStore mention} relationship.
- #
- # @param [Mentionable] mentionable the object to mention/unmention.
- # @return [Boolean]
- def toggle_mention!(mentionable)
- if mentions?(mentionable)
- unmention!(mentionable)
- false
- else
- mention!(mentionable)
- true
- end
- end
-
- # Specifies if self mentions a {Mentionable} object.
- #
- # @param [Mentionable] mentionable the {Mentionable} object to test against.
- # @return [Boolean]
- def mentions?(mentionable)
- ensure_mentionable!(mentionable)
- !self.mentions.where(:mentionable_type => mentionable.class.table_name.classify, :mentionable_id => mentionable.id).empty?
- end
-
- # Returns a scope of the {Mentionable}s mentioned by self.
- #
- # @param [Class] klass the {Mentionable} class to be included in the scope. e.g. `User`.
- # @return [ActiveRecord::Relation]
- def mentionees(klass)
- klass = klass.to_s.singularize.camelize.constantize unless klass.is_a?(Class)
- klass.joins("INNER JOIN mentions ON mentions.mentionable_id = #{klass.to_s.tableize}.id AND mentions.mentionable_type = '#{klass.to_s}'").
- where("mentions.mentioner_type = '#{self.class.to_s}'").
- where("mentions.mentioner_id = #{self.id}")
- end
-
- private
- def ensure_mentionable!(mentionable)
- raise ArgumentError, "#{mentionable} is not mentionable!" unless mentionable.is_mentionable?
- end
-
- end
- end
-end
View
143 lib/socialization/stores/active_record/follow_store.rb
@@ -0,0 +1,143 @@
+module Socialization
+ module ActiveRecordStores
+ class FollowStore < ActiveRecord::Base
+ belongs_to :follower, :polymorphic => true
+ belongs_to :followable, :polymorphic => true
+
+ scope :followed_by, lambda { |follower| where(
+ :follower_type => follower.class.table_name.classify,
+ :follower_id => follower.id)
+ }
+
+ scope :following, lambda { |followable| where(
+ :followable_type => followable.class.table_name.classify,
+ :followable_id => followable.id)
+ }
+
+ @@after_create_hook = nil
+ @@after_destroy_hook = nil
+
+ class << self
+ def table_name
+ 'follows'
+ end
+
+ def follow!(follower, followable)
+ unless follows?(follower, followable)
+ Follow.create! do |follow|
+ follow.follower = follower
+ follow.followable = followable
+ end
+ call_after_create_hook(follower, followable)
+ follower.touch if [:all, :follower].include?(touch) && follower.respond_to?(:touch)
+ followable.touch if [:all, :followable].include?(touch) && followable.respond_to?(:touch)
+ true
+ else
+ false
+ end
+ end
+
+ def unfollow!(follower, followable)
+ if follows?(follower, followable)
+ follow_for(follower, followable).destroy_all
+ call_after_destroy_hook(follower, followable)
+ follower.touch if [:all, :follower].include?(touch) && follower.respond_to?(:touch)
+ followable.touch if [:all, :followable].include?(touch) && followable.respond_to?(:touch)
+ true
+ else
+ false
+ end
+ end
+
+ def follows?(follower, followable)
+ !follow_for(follower, followable).empty?
+ end
+
+ # Returns an ActiveRecord::Relation of all the followers of a certain type that are following followable
+ def followers_relation(followable, klass, opts = {})
+ rel = klass.where(:id =>
+ Follow.select(:follower_id).
+ where(:follower_type => klass.table_name.classify).
+ where(:followable_type => followable.class.to_s).
+ where(:followable_id => followable.id)
+ )
+
+ if opts[:pluck]
+ rel.pluck(opts[:pluck])
+ else
+ rel
+ end
+ end
+
+ # Returns all the followers of a certain type that are following followable
+ def followers(followable, klass, opts = {})
+ rel = followers_relation(followable, klass, opts)
+ if rel.is_a?(ActiveRecord::Relation)
+ rel.all
+ else
+ rel
+ end
+ end
+
+ # Returns an ActiveRecord::Relation of all the followables of a certain type that are followed by follower
+ def followables_relation(follower, klass, opts = {})
+ rel = klass.where(:id =>
+ Follow.select(:followable_id).
+ where(:followable_type => klass.table_name.classify).
+ where(:follower_type => follower.class.to_s).
+ where(:follower_id => follower.id)
+ )
+
+ if opts[:pluck]
+ rel.pluck(opts[:pluck])
+ else
+ rel
+ end
+ end
+
+ # Returns all the followables of a certain type that are followed by follower
+ def followables(follower, klass, opts = {})
+ rel = followables_relation(follower, klass, opts)
+ if rel.is_a?(ActiveRecord::Relation)
+ rel.all
+ else
+ rel
+ end
+ end
+
+ def touch(what = nil)
+ if what.nil?
+ @touch || false
+ else
+ raise ArgumentError unless [:all, :follower, :followable, false, nil].include?(what)
+ @touch = what
+ end
+ end
+
+ def after_follow(method)
+ raise ArgumentError unless method.is_a?(Symbol) || method.nil?
+ @after_create_hook = method
+ end
+
+ def after_unfollow(method)
+ raise ArgumentError unless method.is_a?(Symbol) || method.nil?
+ @after_destroy_hook = method
+ end
+
+ private
+ def call_after_create_hook(follower, followable)
+ self.send(@after_create_hook, follower, followable) if @after_create_hook
+ end
+
+ def call_after_destroy_hook(follower, followable)
+ self.send(@after_destroy_hook, follower, followable) if @after_destroy_hook
+ end
+
+ def follow_for(follower, followable)
+ followed_by(follower).following(followable)
+ end
+ end # class << self
+
+ end
+ end
+end
View
143 lib/socialization/stores/active_record/like_store.rb
@@ -0,0 +1,143 @@
+module Socialization
+ module ActiveRecordStores
+ class LikeStore < ActiveRecord::Base
+ belongs_to :liker, :polymorphic => true
+ belongs_to :likeable, :polymorphic => true
+
+ scope :liked_by, lambda { |liker| where(
+ :liker_type => liker.class.table_name.classify,
+ :liker_id => liker.id)
+ }
+
+ scope :liking, lambda { |likeable| where(
+ :likeable_type => likeable.class.table_name.classify,
+ :likeable_id => likeable.id)
+ }
+
+ @@after_create_hook = nil
+ @@after_destroy_hook = nil
+
+ class << self
+ def table_name
+ 'likes'
+ end
+
+ def like!(liker, likeable)
+ unless likes?(liker, likeable)
+ Like.create! do |like|
+ like.liker = liker
+ like.likeable = likeable
+ end
+ call_after_create_hook(liker, likeable)
+ liker.touch if [:all, :liker].include?(touch) && liker.respond_to?(:touch)
+ likeable.touch if [:all, :likeable].include?(touch) && likeable.respond_to?(:touch)
+ true
+ else
+ false
+ end
+ end
+
+ def unlike!(liker, likeable)
+ if likes?(liker, likeable)
+ like_for(liker, likeable).destroy_all
+ call_after_destroy_hook(liker, likeable)
+ liker.touch if [:all, :liker].include?(touch) && liker.respond_to?(:touch)
+ likeable.touch if [:all, :likeable].include?(touch) && likeable.respond_to?(:touch)
+ true
+ else
+ false
+ end
+ end
+
+ def likes?(liker, likeable)
+ !like_for(liker, likeable).empty?
+ end
+
+ # Returns an ActiveRecord::Relation of all the likers of a certain type that are liking likeable
+ def likers_relation(likeable, klass, opts = {})
+ rel = klass.where(:id =>
+ Like.select(:liker_id).
+ where(:liker_type => klass.table_name.classify).
+ where(:likeable_type => likeable.class.to_s).
+ where(:likeable_id => likeable.id)
+ )
+
+ if opts[:pluck]
+ rel.pluck(opts[:pluck])
+ else
+ rel
+ end
+ end
+
+ # Returns all the likers of a certain type that are liking likeable
+ def likers(likeable, klass, opts = {})
+ rel = likers_relation(likeable, klass, opts)
+ if rel.is_a?(ActiveRecord::Relation)
+ rel.all
+ else
+ rel
+ end
+ end
+
+ # Returns an ActiveRecord::Relation of all the likeables of a certain type that are liked by liker
+ def likeables_relation(liker, klass, opts = {})
+ rel = klass.where(:id =>
+ Like.select(:likeable_id).
+ where(:likeable_type => klass.table_name.classify).
+ where(:liker_type => liker.class.to_s).
+ where(:liker_id => liker.id)
+ )
+
+ if opts[:pluck]
+ rel.pluck(opts[:pluck])
+ else
+ rel
+ end
+ end
+
+ # Returns all the likeables of a certain type that are liked by liker
+ def likeables(liker, klass, opts = {})
+ rel = likeables_relation(liker, klass, opts)
+ if rel.is_a?(ActiveRecord::Relation)
+ rel.all
+ else
+ rel
+ end
+ end
+
+ def touch(what = nil)
+ if what.nil?
+ @touch || false
+ else
+ raise ArgumentError unless [:all, :liker, :likeable, false, nil].include?(what)
+ @touch = what
+ end
+ end
+
+ def after_like(method)
+ raise ArgumentError unless method.is_a?(Symbol) || method.nil?
+ @after_create_hook = method
+ end
+
+ def after_unlike(method)
+ raise ArgumentError unless method.is_a?(Symbol) || method.nil?
+ @after_destroy_hook = method
+ end
+
+ private
+ def call_after_create_hook(liker, likeable)
+ self.send(@after_create_hook, liker, likeable) if @after_create_hook
+ end
+
+ def call_after_destroy_hook(liker, likeable)
+ self.send(@after_destroy_hook, liker, likeable) if @after_destroy_hook
+ end
+
+ def like_for(liker, likeable)
+ liked_by(liker).liking( likeable)
+ end
+ end # class << self
+
+ end
+ end
+end
View
143 lib/socialization/stores/active_record/mention_store.rb
@@ -0,0 +1,143 @@
+module Socialization
+ module ActiveRecordStores
+ class MentionStore < ActiveRecord::Base
+ belongs_to :mentioner, :polymorphic => true
+ belongs_to :mentionable, :polymorphic => true
+
+ scope :mentioned_by, lambda { |mentioner| where(
+ :mentioner_type => mentioner.class.table_name.classify,
+ :mentioner_id => mentioner.id)
+ }
+
+ scope :mentioning, lambda { |mentionable| where(
+ :mentionable_type => mentionable.class.table_name.classify,
+ :mentionable_id => mentionable.id)
+ }
+
+ @@after_create_hook = nil
+ @@after_destroy_hook = nil
+
+ class << self
+ def table_name
+ 'mentions'
+ end
+
+ def mention!(mentioner, mentionable)
+ unless mentions?(mentioner, mentionable)
+ Mention.create! do |mention|
+ mention.mentioner = mentioner
+ mention.mentionable = mentionable
+ end
+ call_after_create_hook(mentioner, mentionable)
+ mentioner.touch if [:all, :mentioner].include?(touch) && mentioner.respond_to?(:touch)
+ mentionable.touch if [:all, :mentionable].include?(touch) && mentionable.respond_to?(:touch)
+ true
+ else
+ false
+ end
+ end
+
+ def unmention!(mentioner, mentionable)
+ if mentions?(mentioner, mentionable)
+ mention_for(mentioner, mentionable).destroy_all
+ call_after_destroy_hook(mentioner, mentionable)
+ mentioner.touch if [:all, :mentioner].include?(touch) && mentioner.respond_to?(:touch)
+ mentionable.touch if [:all, :mentionable].include?(touch) && mentionable.respond_to?(:touch)
+ true
+ else
+ false
+ end
+ end
+
+ def mentions?(mentioner, mentionable)
+ !mention_for(mentioner, mentionable).empty?
+ end
+
+ # Returns an ActiveRecord::Relation of all the mentioners of a certain type that are mentioning mentionable
+ def mentioners_relation(mentionable, klass, opts = {})
+ rel = klass.where(:id =>
+ Mention.select(:mentioner_id).
+ where(:mentioner_type => klass.table_name.classify).
+ where(:mentionable_type => mentionable.class.to_s).
+ where(:mentionable_id => mentionable.id)
+ )
+
+ if opts[:pluck]
+ rel.pluck(opts[:pluck])
+ else
+ rel
+ end
+ end
+
+ # Returns all the mentioners of a certain type that are mentioning mentionable
+ def mentioners(mentionable, klass, opts = {})
+ rel = mentioners_relation(mentionable, klass, opts)
+ if rel.is_a?(ActiveRecord::Relation)
+ rel.all
+ else
+ rel
+ end
+ end
+
+ # Returns an ActiveRecord::Relation of all the mentionables of a certain type that are mentioned by mentioner
+ def mentionables_relation(mentioner, klass, opts = {})
+ rel = klass.where(:id =>
+ Mention.select(:mentionable_id).
+ where(:mentionable_type => klass.table_name.classify).
+ where(:mentioner_type => mentioner.class.to_s).
+ where(:mentioner_id => mentioner.id)
+ )
+
+ if opts[:pluck]
+ rel.pluck(opts[:pluck])
+ else
+ rel
+ end
+ end
+
+ # Returns all the mentionables of a certain type that are mentioned by mentioner
+ def mentionables(mentioner, klass, opts = {})
+ rel = mentionables_relation(mentioner, klass, opts)
+ if rel.is_a?(ActiveRecord::Relation)
+ rel.all
+ else
+ rel
+ end
+ end
+
+ def touch(what = nil)
+ if what.nil?
+ @touch || false
+ else
+ raise ArgumentError unless [:all, :mentioner, :mentionable, false, nil].include?(what)
+ @touch = what
+ end
+ end
+
+ def after_mention(method)
+ raise ArgumentError unless method.is_a?(Symbol) || method.nil?
+ @after_create_hook = method
+ end
+
+ def after_unmention(method)
+ raise ArgumentError unless method.is_a?(Symbol) || method.nil?
+ @after_destroy_hook = method
+ end
+
+ private
+ def call_after_create_hook(mentioner, mentionable)
+ self.send(@after_create_hook, mentioner, mentionable) if @after_create_hook
+ end
+
+ def call_after_destroy_hook(mentioner, mentionable)
+ self.send(@after_destroy_hook, mentioner, mentionable) if @after_destroy_hook
+ end
+
+ def mention_for(mentioner, mentionable)
+ mentioned_by(mentioner).mentioning(mentionable)
+ end
+ end # class << self
+
+ end
+ end
+end
View
38 lib/socialization/victims/followable.rb
@@ -0,0 +1,38 @@
+module ActiveRecord
+ class Base
+ def is_followable?
+ false
+ end
+ end
+end
+
+module Socialization
+ module Followable
+ extend ActiveSupport::Concern
+
+ included do
+ # Specifies if self can be followed.
+ #
+ # @return [Boolean]
+ def is_followable?
+ true
+ end
+
+ # Specifies if self is followed by a {Follower} object.
+ #
+ # @return [Boolean]
+ def followed_by?(follower)
+ raise ArgumentError, "#{follower} is not follower!" unless follower.respond_to?(:is_follower?) && follower.is_follower?
+ Follow.follows?(follower, self)
+ end
+
+ # Returns a scope of the {Follower}s following self.
+ #
+ # @param [Class] klass the {Follower} class to be included in the scope. e.g. `User`
+ # @return [Array<Follower, Numeric>] An array of Follower objects or IDs
+ def followers(klass, opts = {})
+ Follow.followers(self, klass, opts)
+ end
+ end
+ end
+end
View
38 lib/socialization/victims/likeable.rb
@@ -0,0 +1,38 @@
+module ActiveRecord
+ class Base
+ def is_likeable?
+ false
+ end
+ end
+end
+
+module Socialization
+ module Likeable
+ extend ActiveSupport::Concern
+
+ included do
+ # Specifies if self can be liked.
+ #
+ # @return [Boolean]
+ def is_likeable?
+ true
+ end
+
+ # Specifies if self is liked by a {Liker} object.
+ #
+ # @return [Boolean]
+ def liked_by?(liker)
+ raise ArgumentError, "#{liker} is not liker!" unless liker.respond_to?(:is_liker?) && liker.is_liker?
+ Like.likes?(liker, self)
+ end
+
+ # Returns a scope of the {Liker}s likeing self.
+ #
+ # @param [Class] klass the {Liker} class to be included in the scope. e.g. `User`.
+ # @return [Array<Liker, Numeric>] An array of Liker objects or IDs
+ def likers(klass, opts = {})
+ Like.likers(self, klass, opts)
+ end
+ end
+ end
+end
View
38 lib/socialization/victims/mentionable.rb
@@ -0,0 +1,38 @@
+module ActiveRecord
+ class Base
+ def is_mentionable?
+ false
+ end
+ end
+end
+
+module Socialization
+ module Mentionable
+ extend ActiveSupport::Concern
+
+ included do
+ # Specifies if self can be mentioned.
+ #
+ # @return [Boolean]
+ def is_mentionable?
+ true
+ end
+
+ # Specifies if self is mentioned by a {Mentioner} object.
+ #
+ # @return [Boolean]
+ def mentioned_by?(mentioner)
+ raise ArgumentError, "#{mentioner} is not mentioner!" unless mentioner.respond_to?(:is_mentioner?) && mentioner.is_mentioner?
+ Mention.mentions?(mentioner, self)
+ end
+
+ # Returns a scope of the {Mentioner}s mentioning self.
+ #
+ # @param [Class] klass the {Mentioner} class to be included in the scope. e.g. `User`.
+ # @return [Array<Mentioner, Numeric>] An array of Mentioner objects or IDs
+ def mentioners(klass, opts = {})
+ Mention.mentioners(self, klass, opts)
+ end
+ end
+ end
+end
View
74 test/actors/follower_test.rb
@@ -0,0 +1,74 @@
+require File.expand_path(File.dirname(__FILE__))+'/../test_helper'
+
+class FollowerTest < Test::Unit::TestCase
+ context "Follower" do
+ setup do
+ @follower = ImAFollower.new
+ @followable = ImAFollowable.create
+ end
+
+ context "#is_follower" do
+ should "return true" do
+ assert_true @follower.is_follower?
+ end
+ end
+
+ context "#follow!" do
+ should "not accept non-followables" do
+ assert_raise(ArgumentError) { @follower.follow!(:foo) }
+ end
+
+ should "call Follow.follow!" do
+ Follow.expects(:follow!).with(@follower, @followable).once
+ @follower.follow!(@followable)
+ end
+ end
+
+ context "#unfollow!" do
+ should "not accept non-followables" do
+ assert_raise(ArgumentError) { @follower.unfollow!(:foo) }
+ end
+
+ should "call Follow.follow!" do
+ Follow.expects(:unfollow!).with(@follower, @followable).once
+ @follower.unfollow!(@followable)
+ end
+ end
+
+ context "#toggle_follow!" do
+ should "not accept non-followables" do
+ assert_raise(ArgumentError) { @follower.unfollow!(:foo) }
+ end
+
+ should "unfollow when following" do
+ @follower.expects(:follows?).with(@followable).once.returns(true)
+ @follower.expects(:unfollow!).with(@followable).once
+ @follower.toggle_follow!(@followable)
+ end
+
+ should "follow when not following" do
+ @follower.expects(:follows?).with(@followable).once.returns(false)
+ @follower.expects(:follow!).with(@followable).once
+ @follower.toggle_follow!(@followable)
+ end
+ end
+
+ context "#follows?" do
+ should "not accept non-followables" do
+ assert_raise(ArgumentError) { @follower.unfollow!(:foo) }
+ end
+
+ should "call Follow.follows?" do
+ Follow.expects(:follows?).with(@follower, @followable).once
+ @follower.follows?(@followable)
+ end
+ end
+
+ context "#followables" do
+ should "call Follow.followables" do
+ Follow.expects(:followables).with(@follower, @followable.class, { :foo => :bar })
+ @follower.followables(@followable.class, { :foo => :bar })
+ end
+ end
+ end
+end
View
74 test/actors/liker_test.rb
@@ -0,0 +1,74 @@
+require File.expand_path(File.dirname(__FILE__))+'/../test_helper'
+
+class LikerTest < Test::Unit::TestCase
+ context "Liker" do
+ setup do
+ @liker = ImALiker.new
+ @likeable = ImALikeable.create
+ end
+
+ context "#is_liker" do
+ should "return true" do
+ assert_true @liker.is_liker?
+ end
+ end
+
+ context "#like!" do
+ should "not accept non-likeables" do
+ assert_raise(ArgumentError) { @liker.like!(:foo) }
+ end
+
+ should "call Like.like!" do
+ Like.expects(:like!).with(@liker, @likeable).once
+ @liker.like!(@likeable)
+ end
+ end
+
+ context "#unlike!" do
+ should "not accept non-likeables" do
+ assert_raise(ArgumentError) { @liker.unlike!(:foo) }
+ end
+
+ should "call Like.like!" do
+ Like.expects(:unlike!).with(@liker, @likeable).once
+ @liker.unlike!(@likeable)
+ end
+ end
+
+ context "#toggle_like!" do
+ should "not accept non-likeables" do
+ assert_raise(ArgumentError) { @liker.unlike!(:foo) }
+ end
+
+ should "unlike when likeing" do
+ @liker.expects(:likes?).with(@likeable).once.returns(true)
+ @liker.expects(:unlike!).with(@likeable).once
+ @liker.toggle_like!(@likeable)
+ end
+
+ should "like when not likeing" do
+ @liker.expects(:likes?).with(@likeable).once.returns(false)
+ @liker.expects(:like!).with(@likeable).once
+ @liker.toggle_like!(@likeable)
+ end
+ end
+
+ context "#likes?" do
+ should "not accept non-likeables" do
+ assert_raise(ArgumentError) { @liker.unlike!(:foo) }
+ end
+
+ should "call Like.likes?" do
+ Like.expects(:likes?).with(@liker, @likeable).once
+ @liker.likes?(@likeable)
+ end
+ end
+
+ context "#likeables" do
+ should "call Like.likeables" do
+ Like.expects(:likeables).with(@liker, @likeable.class, { :foo => :bar })
+ @liker.likeables(@likeable.class, { :foo => :bar })
+ end
+ end
+ end
+end
View
74 test/actors/mentioner_test.rb
@@ -0,0 +1,74 @@
+require File.expand_path(File.dirname(__FILE__))+'/../test_helper'
+
+class MentionerTest < Test::Unit::TestCase
+ context "Mentioner" do
+ setup do
+ @mentioner = ImAMentioner.new
+ @mentionable = ImAMentionable.create
+ end
+
+ context "#is_mentioner" do
+ should "return true" do
+ assert_true @mentioner.is_mentioner?
+ end
+ end
+
+ context "#mention!" do
+ should "not accept non-mentionables" do
+ assert_raise(ArgumentError) { @mentioner.mention!(:foo) }
+ end
+
+ should "call Mention.mention!" do
+ Mention.expects(:mention!).with(@mentioner, @mentionable).once
+ @mentioner.mention!(@mentionable)
+ end
+ end
+
+ context "#unmention!" do
+ should "not accept non-mentionables" do
+ assert_raise(ArgumentError) { @mentioner.unmention!(:foo) }
+ end
+
+ should "call Mention.mention!" do
+ Mention.expects(:unmention!).with(@mentioner, @mentionable).once
+ @mentioner.unmention!(@mentionable)
+ end
+ end
+
+ context "#toggle_mention!" do
+ should "not accept non-mentionables" do
+ assert_raise(ArgumentError) { @mentioner.unmention!(:foo) }
+ end
+
+ should "unmention when mentioning" do
+ @mentioner.expects(:mentions?).with(@mentionable).once.returns(true)
+ @mentioner.expects(:unmention!).with(@mentionable).once
+ @mentioner.toggle_mention!(@mentionable)
+ end
+
+ should "mention when not mentioning" do
+ @mentioner.expects(:mentions?).with(@mentionable).once.returns(false)
+ @mentioner.expects(:mention!).with(@mentionable).once
+ @mentioner.toggle_mention!(@mentionable)
+ end
+ end
+
+ context "#mentions?" do
+ should "not accept non-mentionables" do
+ assert_raise(ArgumentError) { @mentioner.unmention!(:foo) }
+ end
+
+ should "call Mention.mentions?" do
+ Mention.expects(:mentions?).with(@mentioner, @mentionable).once
+ @mentioner.mentions?(@mentionable)
+ end
+ end
+
+ context "#mentionables" do
+ should "call Mention.mentionables" do
+ Mention.expects(:mentionables).with(@mentioner, @mentionable.class, { :foo => :bar })
+ @mentioner.mentionables(@mentionable.class, { :foo => :bar })
+ end
+ end
+ end
+end
View
166 test/follow_test.rb
@@ -1,166 +0,0 @@
-require File.expand_path(File.dirname(__FILE__))+'/test_helper'
-
-class FollowTest < Test::Unit::TestCase
- context "a Follower" do
- setup do
- seed
- end
-
- should "be follower" do
- assert_true @follower1.is_follower?
- end
-
- should "be able to follow a Followable" do
- assert @follower1.follow!(@followable1)
- assert_true @follower1.follows?(@followable1)
- assert_false @follower2.follows?(@followable1)
- end
-
- should "be able to unfollow a Followable" do
- Follow.create :follower => @follower1, :followable => @followable1
- assert @follower1.unfollow!(@followable1)
- assert_false @follower1.follows?(@followable1)
- end
-
- should "not be able to follow the same thing twice" do
- assert @follower1.follow!(@followable1)
-
- assert_raise ActiveRecord::RecordInvalid do
- @follower1.follow!(@followable1)
- end
- end
-
- should "not be able to unfollow something that is not followed" do
- assert_raise ActiveRecord::RecordNotFound do
- @follower1.unfollow!(@followable1)
- end
- end
-
- should "be able to toggle following on/off" do
- assert_false @follower1.follows?(@followable1)
- assert_true @follower1.toggle_follow!(@followable1)
- assert_true @follower1.follows?(@followable1)
- assert_false @follower1.toggle_follow!(@followable1)
- assert_false @follower1.follows?(@followable1)
- assert_true @follower1.toggle_follow!(@followable1)
- assert_true @follower1.follows?(@followable1)
- end
-
- should "expose a list of its followees" do
- Follow.create :follower => @follower1, :followable => @followable1
- assert @follower1.followees(ImAFollowable).is_a?(ActiveRecord::Relation)
- assert_equal [@followable1], @follower1.followees(ImAFollowable).all
-
- assert_equal @follower1.followees(ImAFollowable), @follower1.followees(:im_a_followables)
- assert_equal @follower1.followees(ImAFollowable), @follower1.followees("im_a_followable")
- end
- end
-
- context "a Followable" do
- setup do
- seed
- end
-
- should "be followable" do
- assert_true @followable1.is_followable?
- end
-
- should "be able to determine who follows it" do
- Follow.create :follower => @follower1, :followable => @followable1
- assert_true @followable1.followed_by?(@follower1)
- assert_false @followable1.followed_by?(@follower2)
- end
-
- should "expose a list of its followers" do
- Follow.create :follower => @follower1, :followable => @followable1
- assert @followable1.followers(ImAFollower).is_a?(ActiveRecord::Relation)
- assert_equal [@follower1], @followable1.followers(ImAFollower).all
-
- assert_equal @followable1.followers(ImAFollower), @followable1.followers(:im_a_followers)
- assert_equal @followable1.followers(ImAFollower), @followable1.followers("im_a_follower")
- end
-
- should "expose followings" do
- Follow.create :follower => @follower1, :followable => @followable1
- followings = @followable1.followings
- assert_equal 1, followings.size
- assert followings.first.is_a?(Follow)
- end
- end
-
- context "Deleting a Follower" do
- setup do
- seed
- @follower1.follow!(@followable1)
- end
-
- should "delete its Follow records" do
- @follower1.destroy
- assert_false @followable1.followed_by?(@follower1)
- end
- end
-
- context "Deleting a Followable" do
- setup do
- seed
- @follower1.follow!(@followable1)
- end
-
- should "delete its Follow records" do
- @followable1.destroy
- assert_false @follower1.follows?(@followable1)
- end
- end
-
- context "Virgin ActiveRecord::Base objects" do
- setup do
- @foo = Vanilla.new
- end
-
- should "not be follower" do
- assert_false @foo.is_follower?
- end
-
- should "not be followable" do
- assert_false @foo.is_followable?
- end
- end
-
- context "acts_as_follow_store" do
- should "touch associated record when touch_follower and/or touch_followable are set" do
- class Foo < ActiveRecord::Base
- self.table_name = 'follows'
- acts_as_follow_store :touch_follower => true, :touch_followable => true
- end
- f = Foo.new
- assert f.methods.map {|x| x.to_s}.include?('belongs_to_touch_after_save_or_destroy_for_followable')
- assert f.methods.map {|x| x.to_s}.include?('belongs_to_touch_after_save_or_destroy_for_follower')
- end
- end
-
- context "Inherited models" do
- setup do
- @follower = ImAFollower.create
- @followable = ImAFollowable.create
- @follower_child = ImAFollowerChild.create
- @followable_child = ImAFollowableChild.create
- end
-
- should "be able to follow a model inheriting from followable" do
- assert @follower.follow!(@followable_child)
- assert_true @follower.follows?(@followable_child)
- end
-
- should "be able to be followed by a model inheriting from follower" do
- assert @follower_child.follow!(@followable)
- assert_true @follower_child.follows?(@followable)
- end
- end
-
- def seed
- @follower1 = ImAFollower.create
- @follower2 = ImAFollower.create
- @followable1 = ImAFollowable.create
- @followable2 = ImAFollowable.create
- end
-end
View
165 test/like_test.rb
@@ -1,165 +0,0 @@
-require File.expand_path(File.dirname(__FILE__))+'/test_helper'
-
-class LikeTest < Test::Unit::TestCase
- context "a Liker" do
- setup do
- seed
- end
-
- should "be liker" do
- assert_true @liker1.is_liker?
- end
-
- should "be able to like a Likeable" do
- assert @liker1.like!(@likeable1)
- assert_true @liker1.likes?(@likeable1)
- assert_false @liker2.likes?(@likeable1)
- end
-
- should "be able to unlike a Likeable" do
- Like.create :liker => @liker1, :likeable => @likeable1
- assert @liker1.unlike!(@likeable1)
- assert_false @liker1.likes?(@likeable1)
- end
-
- should "not be able to like the same thing twice" do
- assert @liker1.like!(@likeable1)
-
- assert_raise ActiveRecord::RecordInvalid do
- @liker1.like!(@likeable1)
- end
- end
-
- should "not be able to unlike something that is not liked" do
- assert_raise ActiveRecord::RecordNotFound do
- @liker1.unlike!(@likeable1)
- end
- end
-
- should "be able to toggle likes on/off" do
- assert_false @liker1.likes?(@likeable1)
- assert_true @liker1.toggle_like!(@likeable1)
- assert_true @liker1.likes?(@likeable1)
- assert_false @liker1.toggle_like!(@likeable1)
- assert_false @liker1.likes?(@likeable1)
- assert_true @liker1.toggle_like!(@likeable1)
- assert_true @liker1.likes?(@likeable1)
- end
-
- should "expose a list of its likes" do
- Like.create :liker => @liker1, :likeable => @likeable1
- assert @liker1.likees(ImALikeable).is_a?(ActiveRecord::Relation)
- assert_equal [@likeable1], @liker1.likees(ImALikeable).all
-
- assert_equal @liker1.likees(ImALikeable), @liker1.likees(:im_a_likeables)
- assert_equal @liker1.likees(ImALikeable), @liker1.likees("im_a_likeable")
- end
- end
-
- context "a Likeable" do
- setup do
- seed
- end
-
- should "be likeable" do
- assert_true @likeable1.is_likeable?
- end
-
- should "be able to determine who likes it" do
- Like.create :liker => @liker1, :likeable => @likeable1
- assert_true @likeable1.liked_by?(@liker1)
- assert_false @likeable1.liked_by?(@liker2)
- end
-
- should "expose a list of its likers" do
- Like.create :liker => @liker1, :likeable => @likeable1
- assert @likeable1.likers(ImALiker).is_a?(ActiveRecord::Relation)
- assert_equal [@liker1], @likeable1.likers(ImALiker).all
-
- assert_equal @likeable1.likers(ImALiker), @likeable1.likers(:im_a_likers)
- assert_equal @likeable1.likers(ImALiker), @likeable1.likers("im_a_liker")
- end
-
- should "expose likings" do
- Like.create :liker => @liker1, :likeable => @likeable1
- likings = @likeable1.likings
- assert_equal 1, likings.size
- assert likings.first.is_a?(Like)
- end
- end
-
- context "Deleting a Liker" do
- setup do
- seed
- @liker1.like!(@likeable1)
- end
-
- should "delete its Like records" do
- @liker1.destroy
- assert_false @likeable1.liked_by?(@liker1)
- end
- end
-
- context "Deleting a Likeable" do
- setup do
- seed
- @liker1.like!(@likeable1)
- end
-
- should "delete its Like records" do
- @likeable1.destroy
- assert_false @liker1.likes?(@likeable1)
- end
- end
-
- context "Virgin ActiveRecord::Base objects" do
- setup do
- @foo = Vanilla.new
- end
-
- should "not be liker" do
- assert_false @foo.is_liker?
- end
-
- should "not be likeable" do
- assert_false @foo.is_likeable?
- end
- end
-
- context "acts_as_like_store" do
- should "touch associated record when touch_liker and/or touch_likeable are set" do
- class Foo < ActiveRecord::Base
- self.table_name = 'likes'; acts_as_like_store :touch_liker => true, :touch_likeable => true
- end
- f = Foo.new
- assert f.methods.map {|x| x.to_s}.include?('belongs_to_touch_after_save_or_destroy_for_likeable')
- assert f.methods.map {|x| x.to_s}.include?('belongs_to_touch_after_save_or_destroy_for_liker')
- end
- end
-
- context "Inherited models" do
- setup do
- @liker = ImALiker.create
- @likeable = ImALikeable.create
- @liker_child = ImALikerChild.create
- @likeable_child = ImALikeableChild.create
- end
-
- should "be able to like a model inheriting from a Likeable" do
- assert @liker.like!(@likeable_child)
- assert_true @liker.likes?(@likeable_child)
- end
-
- should "be able to be liked by a model inheriting from liker" do
- assert @liker_child.like!(@likeable)
- assert_true @liker_child.likes?(@likeable)
- end
- end
-
- def seed
- @liker1 = ImALiker.create
- @liker2 = ImALiker.create
- @likeable1 = ImALikeable.create
- @likeable2 = ImALikeable.create
- end
-end
View
170 test/mention_test.rb
@@ -1,170 +0,0 @@
-require File.expand_path(File.dirname(__FILE__))+'/test_helper'
-
-class MentionTest < Test::Unit::TestCase
- context "a Mentioner" do
- setup do
- seed
- end
-
- should "be mentioner" do
- assert_true @mentioner1.is_mentioner?
- end
-
- should "be able to mention a Mentionable" do
- assert @mentioner1.mention!(@mentionable1)
- assert_true @mentioner1.mentions?(@mentionable1)
- assert_false @mentioner2.mentions?(@mentionable1)
- end
-
- should "be able to unmention a Mentionable" do
- Mention.create :mentioner => @mentioner1, :mentionable => @mentionable1
- assert @mentioner1.unmention!(@mentionable1)
- assert_false @mentioner1.mentions?(@mentionable1)
- end
-
- should "not be able to mention the same thing twice" do
- assert @mentioner1.mention!(@mentionable1)
-
- assert_raise ActiveRecord::RecordInvalid do
- @mentioner1.mention!(@mentionable1)
- end
- end
-
- should "not be able to unmention something that is not mentionned" do
- assert_raise ActiveRecord::RecordNotFound do
- @mentioner1.unmention!(@mentionable1)
- end
- end
-
- should "be able to mention itself" do
- @mentioner_and_mentionable.mention!(@mentioner_and_mentionable)
- end
-
- should "be able to toggle mentions on/off" do
- assert_false @mentioner1.mentions?(@mentionable1)
- assert_true @mentioner1.toggle_mention!(@mentionable1)
- assert_true @mentioner1.mentions?(@mentionable1)
- assert_false @mentioner1.toggle_mention!(@mentionable1)
- assert_false @mentioner1.mentions?(@mentionable1)
- assert_true @mentioner1.toggle_mention!(@mentionable1)
- assert_true @mentioner1.mentions?(@mentionable1)
- end
-
- should "expose a list of its mentionees" do
- Mention.create :mentioner => @mentioner1, :mentionable => @mentionable1
- assert @mentioner1.mentionees(ImAMentioner).is_a?(ActiveRecord::Relation)
- assert_equal [@mentionable1], @mentioner1.mentionees(ImAMentionable).all
-
- assert_equal @mentioner1.mentionees(ImAMentionable), @mentioner1.mentionees(:im_a_mentionables)
- assert_equal @mentioner1.mentionees(ImAMentionable), @mentioner1.mentionees("im_a_mentionable")
- end
- end
-
- context "a Mentionable" do
- setup do
- seed
- end
-
- should "be mentionable" do
- assert_true @mentionable1.is_mentionable?
- end
-
- should "be able to determine who mentions it" do
- Mention.create :mentioner => @mentioner1, :mentionable => @mentionable1
- assert_true @mentionable1.mentioned_by?(@mentioner1)
- assert_false @mentionable1.mentioned_by?(@mentioner2)
- end
-
- should "expose a list of its mentioners" do
- Mention.create :mentioner => @mentioner1, :mentionable => @mentionable1
- assert @mentionable1.mentioners(ImAMentioner).is_a?(ActiveRecord::Relation)
- assert_equal [@mentioner1], @mentionable1.mentioners(ImAMentioner).all
-
- assert_equal @mentionable1.mentioners(ImAMentioner), @mentionable1.mentioners(:im_a_mentioners)
- assert_equal @mentionable1.mentioners(ImAMentioner), @mentionable1.mentioners("im_a_mentioner")
- end
-
- should "expose mentionings" do
- Mention.create :mentioner => @mentioner1, :mentionable => @mentionable1
- mentionings = @mentionable1.mentionings
- assert_equal 1, mentionings.size
- assert mentionings.first.is_a?(Mention)
- end
- end
-
- context "Deleting a mentioner" do
- setup do
- seed
- @mentioner1.mention!(@mentionable1)
- end
-
- should "delete its Mention records" do
- @mentioner1.destroy
- assert_false @mentionable1.mentioned_by?(@mentioner1)
- end
- end
-
- context "Deleting a Mentionable" do
- setup do
- seed
- @mentioner1.mention!(@mentionable1)
- end
-
- should "delete its Mention records" do
- @mentionable1.destroy
- assert_false @mentioner1.mentions?(@mentionable1)
- end
- end
-
- context "Virgin ActiveRecord::Base objects" do
- setup do
- @foo = Vanilla.new
- end
-
- should "not be mentioner" do
- assert_false @foo.is_mentioner?
- end
-
- should "not be mentionable" do
- assert_false @foo.is_mentionable?
- end
- end
-
- context "acts_as_mention_store" do
- should "touch associated record when touch_mentioner and/or touch_mentionable are set" do
- class Foo < ActiveRecord::Base
- self.table_name = 'mentions'; acts_as_mention_store :touch_mentioner => true, :touch_mentionable => true
- end
- f = Foo.new
- assert f.methods.map {|x| x.to_s}.include?('belongs_to_touch_after_save_or_destroy_for_mentionable')
- assert f.methods.map {|x| x.to_s}.include?('belongs_to_touch_after_save_or_destroy_for_mentioner')
- end
- end
-
- context "Single Table Inheritance" do
- setup do
- @mentioner = ImAMentioner.create
- @mentioner_child = ImAMentionerChild.create
- @mentionable = ImAMentionable.create
- @mentionable_child = ImAMentionableChild.create
- end
-
- should "be able to mention a model inheriting from mentionable" do
- assert @mentioner.mention!(@mentionable_child)
- assert_true @mentioner.mentions?(@mentionable_child)
- end
-
- should "be able to be mentioned by a model inheriting from mentioner" do
- assert @mentioner_child.mention!(@mentionable)
- assert_true @mentioner_child.mentions?(@mentionable)
- end
- end
-
- def seed
- @mentioner1 = ImAMentioner.create
- @mentioner2 = ImAMentioner.create
- @mentionable1 = ImAMentionable.create
- @mentionable2 = ImAMentionable.create
- @mentioner_and_mentionable = ImAMentionerAndMentionable.create
- end
-end
View
117 test/stores/active_record/follow_store_test.rb
@@ -0,0 +1,117 @@
+require File.expand_path(File.dirname(__FILE__))+'/../../test_helper'
+
+class ARFollowStores < Socialization::ActiveRecordStores::FollowStore; end
+
+class ActiveRecordFollowStoreTest < Test::Unit::TestCase
+ context "ActiveRecordStores::FollowStoreTest" do
+