├── .autotest ├── .gitignore ├── .rspec ├── Gemfile ├── README.md ├── Rakefile ├── VERSION ├── autotest └── discover.rb ├── lib ├── likeable.rb └── likeable │ ├── facepile.rb │ ├── like.rb │ ├── module_methods.rb │ └── user_methods.rb ├── license.txt ├── likeable.gemspec └── spec ├── likeable ├── facepile_spec.rb ├── like_spec.rb ├── module_methods_spec.rb ├── setup_spec.rb └── user_methods_spec.rb ├── likeable_spec.rb └── spec_helper.rb /.autotest: -------------------------------------------------------------------------------- 1 | require 'autotest/growl' -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Gemfile.lock 2 | /pkg 3 | /vendor 4 | .rvmrc 5 | .DS_Store 6 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --format nested 2 | --color -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'http://rubygems.org' 2 | gem 'activesupport' 3 | gem 'keytar', '>=1.5.2' 4 | gem 'redis' 5 | 6 | 7 | group :development, :test do 8 | gem 'activerecord', '~>3.0.4' ## not needed if you're just using KeyBuilder 9 | gem 'rake', '~>0.8.7' 10 | gem 'jeweler', '~>1.5.2' 11 | gem "autotest-standalone" 12 | gem "autotest-growl" 13 | end 14 | 15 | group :test do 16 | gem 'sqlite3', '~> 1.3.3' 17 | gem 'rspec', '~> 2.5' 18 | end 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Likeable Has Moved!! 2 | 3 | You can see the most up-to-date version here: https://github.com/schneems/likeable 4 | 5 | # What is it? 6 | 7 | Use Redis to make any Ruby objects Likeable! 8 | 9 | 10 | # Why 11 | 12 | We chose Redis because it is screaming fast, and very simple to work with. By using Redis for likeable we take load off of our relational database and speed up individual calls to retrieve information about the "liked" state of an object. If you're not using Redis in production, and don't want to, there are many other great liking/voting libraries out there such as [thumbs up](https://github.com/brady8/thumbs_up). 13 | 14 | 15 | 16 | RedisRed RedisRedi 17 | RedisRedisRedi RedisRedisRedisR 18 | RedisRedisRedisRedi RedisRedisRedisRedi 19 | RedisRedisRedisRedisRedisRedisRe Redi 20 | RedisRedisRedisRedisRedisRedisRe Redi 21 | RedisRedisRedisRedisRedisRedisRedisR Redi 22 | RedisRedisRedisRedisRedisRedisRedisRedis R 23 | RedisRedisRedisRedisRedisRedisRedisRedisRedi Red 24 | RedisRedisRedisRedisRedisRedisRedisRedisRedisRe R 25 | RedisRedisRedisRedisRedisRedisRedisRedisRedisRedi 26 | RedisRedisRedisRedisRedisRedisRedisRedisRedisRedi 27 | RedisRedisRedisRedisRedisRedisRedisRedisRedisRe 28 | RedisRedisRedisRedisRedisRedisRedisRedisRedis 29 | RedisRedisRedisRedisRedisRedisRedisRedisRe 30 | RedisRedisRedisRedisRedisRedisRedisRe 31 | RedisRedisRedisRedisRedisRedisR 32 | RedisRedisRedisRedisRedis 33 | RedisRedisRedisRedis 34 | RedisRedisRed 35 | RedisRedi 36 | RedisR 37 | Redi 38 | Re 39 | Authors 40 | ======= 41 | [Richard Schneeman](http://schneems.com) for [Gowalla](http://gowalla.com) <3 42 | 43 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'bundler' 3 | begin 4 | Bundler.setup(:default, :development, :test) 5 | rescue Bundler::BundlerError => e 6 | $stderr.puts e.message 7 | $stderr.puts "Run `bundle install` to install missing gems" 8 | exit e.status_code 9 | end 10 | require 'rake' 11 | 12 | require 'jeweler' 13 | Jeweler::Tasks.new do |gem| 14 | # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options 15 | gem.name = "likeable" 16 | gem.homepage = "https://github.com/schneems/Likeable" 17 | gem.license = "MIT" 18 | gem.summary = %Q{Like ruby objects backed by redis} 19 | gem.description = %Q{ 20 | Likeable allows you to make your models...well...likeable using redis. 21 | } 22 | gem.email = "richard.schneeman@gmail.com" 23 | gem.authors = ["Schneems"] 24 | gem.add_development_dependency "rspec" 25 | end 26 | Jeweler::RubygemsDotOrgTasks.new 27 | 28 | require 'rspec/core/rake_task' 29 | RSpec::Core::RakeTask.new(:spec) 30 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 0.1.1 -------------------------------------------------------------------------------- /autotest/discover.rb: -------------------------------------------------------------------------------- 1 | Autotest.add_discovery { "rspec2" } -------------------------------------------------------------------------------- /lib/likeable.rb: -------------------------------------------------------------------------------- 1 | require 'active_support/concern' 2 | require 'keytar' 3 | 4 | 5 | module Likeable 6 | extend ActiveSupport::Concern 7 | 8 | included do 9 | include Keytar 10 | include Likeable::Facepile 11 | define_key :like, :key_case => nil 12 | 13 | if self.respond_to?(:after_destroy) 14 | after_destroy :destroy_all_likes 15 | else 16 | warn "#{self} doesn't support after_destroy callback, likes will not be cleared automatically when object is destroyed" 17 | end 18 | end 19 | 20 | def destroy_all_likes 21 | liked_users.each {|user| self.remove_like_from(user) } 22 | end 23 | 24 | # create a like 25 | # the user who created the like has a reference to the object liked 26 | def add_like_from(user, time = Time.now.to_f) 27 | Likeable.redis.hset(like_key, user.id, time) 28 | Likeable.redis.hset(user.like_key(self.class.to_s.downcase), self.id, time) 29 | like = Like.new(:target => self, :user => user, :time => time) 30 | after_like(like) 31 | clear_memoized_methods(:like_count, :like_user_ids, :liked_user_ids, :liked_users, :likes) 32 | like 33 | end 34 | 35 | def clear_memoized_methods(*methods) 36 | methods.each do |method| 37 | eval("@#{method} = nil") 38 | end 39 | end 40 | 41 | def after_like(like) 42 | Likeable.after_like.call(like) 43 | end 44 | 45 | # removes a like 46 | def remove_like_from(user) 47 | Likeable.redis.hdel(like_key, user.id) 48 | Likeable.redis.hdel(user.like_key(self.class.to_s.downcase), self.id) 49 | clear_memoized_methods(:like_count, :like_user_ids, :liked_user_ids, :liked_users) 50 | end 51 | 52 | def like_count 53 | @like_count ||= @like_user_ids.try(:count) || @likes.try(:count) || Likeable.redis.hlen(like_key) 54 | end 55 | 56 | # get all user ids that have liked a target object 57 | def like_user_ids 58 | @like_user_ids ||= (Likeable.redis.hkeys(like_key)||[]).map(&:to_i) 59 | end 60 | 61 | def liked_users(limit = nil) 62 | @liked_users ||= Likeable.user_class.where(:id => like_user_ids) 63 | end 64 | 65 | def likes 66 | @likes ||= begin 67 | Likeable.redis.hgetall(like_key).collect do |user_id, time| 68 | Like.new(:user_id => user_id, :time => time, :target => self) 69 | end 70 | end 71 | end 72 | 73 | # did given user like the object 74 | def liked_by?(user) 75 | return false unless user 76 | liked_by = @like_user_ids.include?(user.id) if @like_user_ids 77 | liked_by ||= Likeable.redis.hexists(like_key, user.id) 78 | end 79 | 80 | 81 | def likeable_resource_name 82 | Likeable.get_resource_name_for_class(self.class) 83 | end 84 | 85 | 86 | ### Class Methods ### 87 | # ----------------- # 88 | # allows us to setup callbacks when creating likes 89 | # after_like :notify_users 90 | module ClassMethods 91 | 92 | def all_liked_ids_by(user) 93 | key = user.like_key(self.to_s.downcase) 94 | ids = (Likeable.redis.hkeys(key)||[]).map(&:to_i) 95 | end 96 | 97 | def all_liked_by(user) 98 | ids = all_liked_ids_by(user) 99 | self.where(:id => ids) 100 | end 101 | 102 | def after_like(*methods) 103 | define_method(:after_like) do |like| 104 | methods.each do |method| 105 | eval("#{method}(like)") 106 | end 107 | end 108 | end 109 | end 110 | end 111 | 112 | require 'likeable/like' 113 | require 'likeable/facepile' 114 | require 'likeable/user_methods' 115 | require 'likeable/module_methods' 116 | -------------------------------------------------------------------------------- /lib/likeable/facepile.rb: -------------------------------------------------------------------------------- 1 | module Likeable 2 | module Facepile 3 | # returns friend of user who like target 4 | def liked_friends(user, limit = nil) 5 | friend_ids = liked_friend_ids(user) 6 | friend_ids = friend_ids.sample(limit) unless limit.blank? 7 | @liked_friends ||= Likeable.find_many(User, friend_ids) 8 | end 9 | 10 | def liked_friend_ids(user) 11 | @liked_friend_ids ||= like_user_ids & user.friend_ids 12 | end 13 | 14 | def ids_for_facepile(user, limit = Likeable.facepile_default_limit) 15 | ids = liked_friend_ids(user).shuffle + like_user_ids.shuffle # show friends first 16 | ids.uniq.first(limit) 17 | end 18 | 19 | def users_for_facepile(user, limit = Likeable.facepile_default_limit) 20 | return [] if user.blank? 21 | @facepile ||= begin 22 | return nil unless ids = ids_for_facepile(user, limit) 23 | Likeable.find_many(User, ids) 24 | end 25 | end 26 | end 27 | end -------------------------------------------------------------------------------- /lib/likeable/like.rb: -------------------------------------------------------------------------------- 1 | require 'digest/sha1' 2 | 3 | class Likeable::Like 4 | attr_accessor :created_at, :target, :like_user, :user_id 5 | 6 | def initialize(options = {}) 7 | self.created_at = Time.at(options[:time].try(:to_f)||Time.now) 8 | self.target = options[:target] 9 | self.user_id = options[:user].try(:id) || options[:user_id] 10 | self.like_user = options[:user] 11 | end 12 | 13 | def id 14 | Digest::SHA1.hexdigest("#{user_id}#{target.class}#{target.id}#{created_at}") 15 | end 16 | 17 | def user 18 | @user ||= like_user 19 | @user ||= Likeable.find_one(User, user_id) 20 | @user 21 | end 22 | 23 | def to_hash(type=:full) 24 | { 25 | :created_at => created_at.iso8601, 26 | :type => target.class.name.gsub(/^[A-Za-z]+::/, '').underscore.downcase.to_sym, 27 | :target => target.to_hash(type), 28 | :user => user.to_hash(type) 29 | } 30 | end 31 | end -------------------------------------------------------------------------------- /lib/likeable/module_methods.rb: -------------------------------------------------------------------------------- 1 | require 'redis' 2 | 3 | module Likeable 4 | mattr_accessor :facepile_default_limit 5 | self.facepile_default_limit = 9 6 | 7 | 8 | ### Module Methods ### 9 | # ------------------ # 10 | class << self 11 | def classes 12 | (@classes||[]).flatten 13 | end 14 | 15 | def classes=(*args) 16 | @classes = args 17 | end 18 | 19 | # Likeable.model("Highlight") 20 | # ------------------------- # 21 | # turns a string into a model 22 | # "Highlight".constantize # => Highlight; "Hi1i6ht".constantize = #=> false 23 | def model(target_model) 24 | target_model.camelcase.constantize 25 | rescue NameError => ex 26 | return false 27 | end 28 | 29 | # Likeable.find_by_resource_id("highlight", 22) 30 | # ---------------------------------------- # 31 | # # ids)} 73 | @find_many.call(klass, ids) 74 | end 75 | 76 | 77 | def find_one(klass, id) 78 | @find_one ||= lambda {|klass, id| klass.where(:id => id).first} 79 | @find_one.call(klass, id) 80 | end 81 | 82 | def find_one=(find_one) 83 | @find_one = find_one 84 | end 85 | 86 | def user_class 87 | begin 88 | @user_class ||= ::User 89 | rescue NameError 90 | nil 91 | end 92 | end 93 | 94 | def user_class=(klass) 95 | raise ArgumentError, "Argument must be a class" unless klass.is_a?(Class) 96 | @user_class = klass 97 | end 98 | 99 | # Likeable.setup do |like| 100 | # like.redis = Redis.new(#...) 101 | # like.find_one = lambda {|klass, id | klass.where(:id => id)} 102 | # like.find_many = lambda {|klass, ids| klass.where(:id => ids)} 103 | # end 104 | def setup(&block) 105 | yield self unless block.blank? 106 | true 107 | end 108 | end 109 | end 110 | -------------------------------------------------------------------------------- /lib/likeable/user_methods.rb: -------------------------------------------------------------------------------- 1 | module Likeable::UserMethods 2 | extend ActiveSupport::Concern 3 | 4 | included do 5 | include Keytar 6 | define_key :like, :key_case => nil 7 | end 8 | 9 | 10 | def like!(obj) 11 | obj.add_like_from(self) 12 | end 13 | 14 | def unlike!(obj) 15 | obj.remove_like_from(self) 16 | end 17 | 18 | def like?(obj) 19 | obj.liked_by?(self) 20 | end 21 | alias :likes? :like? 22 | 23 | def friend_ids_that_like(obj) 24 | obj.liked_friend_ids(self) 25 | end 26 | 27 | def friends_that_like(obj, limit = nil) 28 | obj.liked_friends(self, limit) 29 | end 30 | 31 | # @user.liked(Spot) 32 | # will return all spots that user has liked 33 | def all_liked(klass) 34 | klass.all_liked_by(self) 35 | end 36 | end -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | licensed under MIT License: 2 | 3 | Copyright (c) 2011 Schneems 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. 23 | -------------------------------------------------------------------------------- /likeable.gemspec: -------------------------------------------------------------------------------- 1 | # Generated by jeweler 2 | # DO NOT EDIT THIS FILE DIRECTLY 3 | # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec' 4 | # -*- encoding: utf-8 -*- 5 | 6 | Gem::Specification.new do |s| 7 | s.name = "likeable" 8 | s.version = "0.1.1" 9 | 10 | s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= 11 | s.authors = ["Schneems"] 12 | s.date = "2011-12-17" 13 | s.description = "\n Likeable allows you to make your models...well...likeable using redis.\n " 14 | s.email = "richard.schneeman@gmail.com" 15 | s.extra_rdoc_files = [ 16 | "README.md" 17 | ] 18 | s.files = [ 19 | ".autotest", 20 | ".rspec", 21 | "Gemfile", 22 | "README.md", 23 | "Rakefile", 24 | "VERSION", 25 | "autotest/discover.rb", 26 | "lib/likeable.rb", 27 | "lib/likeable/facepile.rb", 28 | "lib/likeable/like.rb", 29 | "lib/likeable/module_methods.rb", 30 | "lib/likeable/user_methods.rb", 31 | "license.txt", 32 | "likeable.gemspec", 33 | "spec/likeable/facepile_spec.rb", 34 | "spec/likeable/like_spec.rb", 35 | "spec/likeable/module_methods_spec.rb", 36 | "spec/likeable/setup_spec.rb", 37 | "spec/likeable/user_methods_spec.rb", 38 | "spec/likeable_spec.rb", 39 | "spec/spec_helper.rb" 40 | ] 41 | s.homepage = "https://github.com/schneems/Likeable" 42 | s.licenses = ["MIT"] 43 | s.require_paths = ["lib"] 44 | s.rubygems_version = "1.8.10" 45 | s.summary = "Like ruby objects backed by redis" 46 | s.test_files = [ 47 | "spec/likeable/facepile_spec.rb", 48 | "spec/likeable/like_spec.rb", 49 | "spec/likeable/module_methods_spec.rb", 50 | "spec/likeable/setup_spec.rb", 51 | "spec/likeable/user_methods_spec.rb", 52 | "spec/likeable_spec.rb", 53 | "spec/spec_helper.rb" 54 | ] 55 | 56 | if s.respond_to? :specification_version then 57 | s.specification_version = 3 58 | 59 | if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then 60 | s.add_runtime_dependency(%q, [">= 0"]) 61 | s.add_runtime_dependency(%q, [">= 1.5.2"]) 62 | s.add_runtime_dependency(%q, [">= 0"]) 63 | s.add_development_dependency(%q, ["~> 3.0.4"]) 64 | s.add_development_dependency(%q, ["~> 0.8.7"]) 65 | s.add_development_dependency(%q, ["~> 1.5.2"]) 66 | s.add_development_dependency(%q, [">= 0"]) 67 | s.add_development_dependency(%q, [">= 0"]) 68 | s.add_development_dependency(%q, [">= 0"]) 69 | else 70 | s.add_dependency(%q, [">= 0"]) 71 | s.add_dependency(%q, [">= 1.5.2"]) 72 | s.add_dependency(%q, [">= 0"]) 73 | s.add_dependency(%q, ["~> 3.0.4"]) 74 | s.add_dependency(%q, ["~> 0.8.7"]) 75 | s.add_dependency(%q, ["~> 1.5.2"]) 76 | s.add_dependency(%q, [">= 0"]) 77 | s.add_dependency(%q, [">= 0"]) 78 | s.add_dependency(%q, [">= 0"]) 79 | end 80 | else 81 | s.add_dependency(%q, [">= 0"]) 82 | s.add_dependency(%q, [">= 1.5.2"]) 83 | s.add_dependency(%q, [">= 0"]) 84 | s.add_dependency(%q, ["~> 3.0.4"]) 85 | s.add_dependency(%q, ["~> 0.8.7"]) 86 | s.add_dependency(%q, ["~> 1.5.2"]) 87 | s.add_dependency(%q, [">= 0"]) 88 | s.add_dependency(%q, [">= 0"]) 89 | s.add_dependency(%q, [">= 0"]) 90 | end 91 | end 92 | 93 | -------------------------------------------------------------------------------- /spec/likeable/facepile_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | class CleanTestClassForLikeable 4 | include Likeable 5 | def like_key 6 | "like_key" 7 | end 8 | 9 | def to_hash(*args); {} end 10 | 11 | def foo 12 | end 13 | 14 | def id 15 | @id ||= rand(100) 16 | end 17 | end 18 | 19 | 20 | describe Likeable::Facepile do 21 | before(:each) do 22 | @user = User.new 23 | @target = CleanTestClassForLikeable.new 24 | end 25 | 26 | describe 'facepile' do 27 | before do 28 | @friend_ids = [1,2,3,4] 29 | @like_ids = [3,4,5,6,7,8,9,10,11,12] 30 | @intersection = @friend_ids & @like_ids 31 | end 32 | describe '#ids_for_facepile' do 33 | it 'builds a array of ids with friend ids and randoms if they have liked the object up to the limit' do 34 | @target.should_receive(:liked_friend_ids).with(@user).and_return(@friend_ids) 35 | @target.should_receive(:like_user_ids).and_return(@like_ids) 36 | @target.ids_for_facepile(@user).should include(@intersection.sample) 37 | end 38 | 39 | it 'can be limited' do 40 | limit = 3 41 | @target.should_receive(:liked_friend_ids).and_return(@friend_ids) 42 | @target.should_receive(:like_user_ids).and_return(@like_ids) 43 | @target.ids_for_facepile(@user, limit).count.should eq(limit) 44 | end 45 | end 46 | 47 | describe '#users_for_facepile' do 48 | it 'builds a array of users if they have liked the object' do 49 | @target.should_receive(:ids_for_facepile).and_return(@friend_ids) 50 | User.should_receive(:where).with(:id => @friend_ids) 51 | @target.users_for_facepile(@user) 52 | end 53 | end 54 | 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /spec/likeable/like_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | describe Likeable::Like do 3 | before do 4 | @time = Time.now 5 | end 6 | describe 'attributes' do 7 | it 'stores target, user, and created_at' do 8 | like = Likeable::Like.new(:target => @target, :user => @user, :time => @time) 9 | like.user.should eq(@user) 10 | like.target.should eq(@target) 11 | # Times often fail equality checks due to microsec precision 12 | like.created_at.should be_within(1).of(@time) 13 | end 14 | 15 | it 'converts float time to propper Time object' do 16 | like = Likeable::Like.new(:time => @time.to_f) 17 | like.created_at.should be_within(1).of(@time) 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /spec/likeable/module_methods_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | class CleanTestClassForLikeable 4 | include Likeable 5 | def like_key 6 | "like_key" 7 | end 8 | 9 | def to_hash(*args); {} end 10 | 11 | def foo 12 | end 13 | 14 | def id 15 | @id ||= rand(100) 16 | end 17 | end 18 | 19 | class LikeableIncludedInSetup 20 | def like_key 21 | "like_key" 22 | end 23 | 24 | def id 25 | @id ||= rand(100) 26 | end 27 | end 28 | 29 | describe Likeable::Facepile do 30 | before(:each) do 31 | @user = User.new 32 | @target = CleanTestClassForLikeable.new 33 | end 34 | 35 | describe "module methods" do 36 | describe ".model" do 37 | it "takes a valid class string and turns it into a class" do 38 | klass = CleanTestClassForLikeable 39 | klass_name = klass.to_s 40 | Likeable.model(klass_name).should eq(klass) 41 | end 42 | end 43 | 44 | describe ".find_by_resource_id" do 45 | it "finds an active-record based object on a valid model and id" do 46 | klass = CleanTestClassForLikeable 47 | klass_name = klass.to_s 48 | id = rand(1000) 49 | klass.should_receive(:where).with(:id => id).and_return([]) 50 | Likeable.find_by_resource_id(klass_name, id) 51 | end 52 | 53 | it "will return nil for an invalid object" do 54 | klass = CleanTestClassForLikeable 55 | klass_name = klass.to_s + "this makes this klass_name invalid" 56 | 57 | Likeable.find_by_resource_id(klass_name, rand(1000)).should be_blank 58 | end 59 | end 60 | 61 | end 62 | 63 | end 64 | -------------------------------------------------------------------------------- /spec/likeable/setup_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | 4 | class LikeableIncludedInSetup 5 | 6 | end 7 | 8 | class SetupCleanTestClassForLikeable 9 | include Likeable 10 | def like_key 11 | "like_key" 12 | end 13 | 14 | def to_hash(*args); {} end 15 | 16 | def foo 17 | end 18 | 19 | def id 20 | @id ||= rand(100) 21 | end 22 | end 23 | 24 | describe Likeable do 25 | describe "setup" do 26 | context "when the User class is defined" do 27 | before(:each) do 28 | reload_user! 29 | Likeable.user_class = User 30 | @user = User.new 31 | @target = SetupCleanTestClassForLikeable.new 32 | end 33 | 34 | it "" do 35 | result = "foo" 36 | Likeable.setup 37 | 38 | Likeable.after_like do |like| 39 | result = "after_like_called_successfully" 40 | end 41 | 42 | @user.like! @target 43 | result.should == "after_like_called_successfully" 44 | end 45 | end 46 | 47 | context "when the User class doesn't exist" do 48 | before do 49 | # Need a cleaner way to do this, but the setter 50 | # prevents it 51 | Likeable.instance_variable_set(:@user_class, nil) 52 | unload_user! 53 | end 54 | 55 | after do 56 | build_user! 57 | Likeable.setup 58 | end 59 | 60 | it "won't raise an exception" do 61 | lambda { Likeable.setup }.should_not raise_error 62 | end 63 | end 64 | end 65 | end 66 | -------------------------------------------------------------------------------- /spec/likeable/user_methods_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | class CleanTestClassForLikeable 4 | include Likeable 5 | def like_key 6 | "like_key" 7 | end 8 | 9 | def to_hash(*args); {} end 10 | 11 | def foo 12 | end 13 | 14 | def id 15 | @id ||= rand(100) 16 | end 17 | end 18 | 19 | Likeable.setup 20 | 21 | describe Likeable::UserMethods do 22 | before(:each) do 23 | @user = User.new 24 | @target = CleanTestClassForLikeable.new 25 | end 26 | 27 | describe '#like!' do 28 | it "calls add_like_from in target" do 29 | @target.should_receive(:add_like_from).with(@user) 30 | @user.like! @target 31 | end 32 | end 33 | 34 | describe '#unlike!' do 35 | it "calls remove_like_from in target" do 36 | @target.should_receive(:remove_like_from).with(@user) 37 | @user.unlike! @target 38 | end 39 | end 40 | 41 | describe '#like?' do 42 | it "calls liked_by? in target" do 43 | @target.should_receive(:liked_by?).with(@user) 44 | @user.like? @target 45 | end 46 | end 47 | 48 | describe '#like?' do 49 | it "calls liked_by? in target" do 50 | @target.should_receive(:liked_by?).with(@user) 51 | @user.like? @target 52 | end 53 | end 54 | 55 | describe '#friend_ids_that_like' do 56 | it "calls liked_friend_ids? in target" do 57 | @target.should_receive(:liked_friend_ids).with(@user) 58 | @user.friend_ids_that_like @target 59 | end 60 | end 61 | 62 | describe '#friends_that_like' do 63 | it "calls liked_friends? in target" do 64 | @target.should_receive(:liked_friends).with(@user, nil) 65 | @user.friends_that_like @target 66 | end 67 | end 68 | end 69 | -------------------------------------------------------------------------------- /spec/likeable_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | class CleanTestClassForLikeable 3 | include Likeable 4 | def like_key 5 | "like_key" 6 | end 7 | 8 | def to_hash(*args); {} end 9 | 10 | def foo 11 | end 12 | 13 | def id 14 | @id ||= rand(100) 15 | end 16 | end 17 | 18 | 19 | 20 | 21 | Likeable.setup do |like| 22 | like.find_one = lambda {|klass, id| klass.where(:id => id)} 23 | end 24 | 25 | 26 | describe Likeable do 27 | 28 | before(:each) do 29 | @user = User.new 30 | @target = CleanTestClassForLikeable.new 31 | end 32 | describe 'instance methods' do 33 | 34 | describe "#add_like_from" do 35 | it "creates a like" do 36 | target_class = @target.class.to_s.downcase 37 | user_like_key = "users:like:#{@user.id}:#{target_class}" 38 | time = Time.now.to_f 39 | @user.should_receive(:like_key).with(target_class).and_return(user_like_key) 40 | Likeable.redis.should_receive(:hset).with("like_key", @user.id, time).once 41 | Likeable.redis.should_receive(:hset).with(user_like_key, @target.id, time).once 42 | @target.add_like_from(@user, time) 43 | end 44 | end 45 | 46 | describe "#remove_like_from" do 47 | it "removes a like" do 48 | target_class = @target.class.to_s.downcase 49 | user_like_key = "users:like:#{@user.id}:#{target_class}" 50 | @user.should_receive(:like_key).with(target_class).and_return(user_like_key) 51 | Likeable.redis.should_receive(:hdel).with("like_key", @user.id).once 52 | Likeable.redis.should_receive(:hdel).with(user_like_key, @target.id) 53 | @target.remove_like_from(@user) 54 | end 55 | end 56 | 57 | describe "#liked_users" do 58 | it "finds the users that like it" do 59 | @target.should_receive(:like_user_ids).and_return([1,2]) 60 | User.should_receive(:where).with(:id => [1,2]) 61 | @target.liked_users(@user) 62 | end 63 | end 64 | 65 | describe "#likes" do 66 | it "returns set of likes" do 67 | Likeable.redis.should_receive(:hkeys).with("like_key").once 68 | @target.like_user_ids 69 | end 70 | end 71 | 72 | describe "#liked_by?" do 73 | it "will answer if current user likes target" do 74 | Likeable.redis.should_receive(:hexists).with("like_key", @user.id).once 75 | @target.liked_by?(@user) 76 | end 77 | end 78 | 79 | describe "#liked_friend_ids" do 80 | it "will return all friend ids of user who like target" do 81 | common_value = 3 82 | @target.should_receive(:like_user_ids).and_return([1,2, common_value]) 83 | @user.should_receive(:friend_ids).and_return([common_value]) 84 | @target.liked_friend_ids(@user).should == [common_value] 85 | end 86 | end 87 | 88 | describe "#liked_friends" do 89 | it "will return all friends who like object" do 90 | values = [1] 91 | @target.should_receive(:liked_friend_ids).with(@user).and_return(values) 92 | User.should_receive(:where).with(:id => values) 93 | @target.liked_friends(@user) 94 | end 95 | end 96 | end 97 | 98 | describe "class methods" do 99 | describe 'after_like' do 100 | it 'should be a class method when included' do 101 | CleanTestClassForLikeable.respond_to?(:after_like).should be_true 102 | end 103 | it 'is called after a like is created' do 104 | CleanTestClassForLikeable.after_like(:foo) 105 | @target.should_receive(:foo) 106 | @target.add_like_from(@user) 107 | end 108 | end 109 | end 110 | 111 | end 112 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'active_record' 3 | 4 | 5 | $LOAD_PATH.unshift(File.dirname(__FILE__)) 6 | $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '../..', 'lib')) 7 | 8 | 9 | require 'likeable' 10 | 11 | 12 | def build_user! 13 | eval %Q{ 14 | class User 15 | include Likeable::UserMethods 16 | def id 17 | @time ||= Time.now.to_f.to_s 18 | end 19 | 20 | def self.where(*args) 21 | end 22 | 23 | def friends_ids 24 | [] 25 | end 26 | 27 | def self.after_destroy 28 | end 29 | end 30 | } 31 | end 32 | 33 | def unload_user! 34 | Object.instance_eval{ remove_const :User } 35 | end 36 | 37 | def reload_user! 38 | unload_user! 39 | build_user! 40 | end 41 | 42 | build_user! 43 | 44 | 45 | require 'rspec' 46 | require 'rspec/autorun' 47 | 48 | --------------------------------------------------------------------------------