friendable 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format=d
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm use 1.9.3-p194@friendable --create
@@ -0,0 +1,3 @@
1
+ SimpleCov.start do
2
+ add_filter "spec/"
3
+ end
@@ -0,0 +1,12 @@
1
+ script: "bundle exec rspec spec/"
2
+
3
+ rvm:
4
+ - ree
5
+ - 1.8.7
6
+ - 1.9.2
7
+ - 1.9.3
8
+
9
+ matrix:
10
+ allow_failures:
11
+ - rvm: ree
12
+ - rvm: 1.8.7
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in friendable.gemspec
4
+ gemspec
5
+
6
+ gem 'pry'
7
+ gem 'simplecov'
@@ -0,0 +1,22 @@
1
+ # Copyright (c) 2012 Yuki Nishijima
2
+
3
+ ## MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,118 @@
1
+ # Friendable
2
+
3
+ You don't want to implement friendship functionality with RDB any more? But you think it's still too early to use graphDB? Use Redis!
4
+
5
+ [![Build Status](https://secure.travis-ci.org/yuki24/friendable.png)](http://travis-ci.org/yuki24/friendable) [![Code Climate](https://codeclimate.com/badge.png)](https://codeclimate.com/github/yuki24/friendable)
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ gem 'friendable'
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Next, include `Friendable::UserMethods` inside User class or something like that you use in your application:
18
+
19
+ ```ruby
20
+ class User < ActiveRecord::Base
21
+ include Friendable::UserMethods
22
+
23
+ ...
24
+ end
25
+ ```
26
+
27
+ Create a config file like below to set up Redis connection and save it as `config/initializers/friendable.rb`:
28
+
29
+ ```ruby
30
+ Friendable.redis = Redis.new(host: "localhost", port: 6379)
31
+ ```
32
+
33
+ ## Usage
34
+
35
+ ### Being a friend
36
+
37
+ ```ruby
38
+ user = User.first
39
+ another_user = User.last
40
+
41
+ user.friends # => empty ActiveRecord::Relation
42
+ user.friend?(another_user) # => false
43
+
44
+ # they are now friends!
45
+ current_user.friend!(another_user)
46
+
47
+ user.friend?(another_user) # => true
48
+ user.friends.include?(another_user) # => true
49
+ another_user.friend?(user) # => true
50
+ another_user.friends.include?(user) # => true
51
+ ```
52
+
53
+ You can also pass properties about their relationship. For example, if you want to store where they meet, you can do something like this:
54
+
55
+ ```ruby
56
+ friendship = user.friend!(another_user, :meeting_place => "Red Rock")
57
+
58
+ friendship.meeting_place # => "Red Rock"
59
+ friendship.created_at # => "2012-09-23 14:24:03"
60
+ friendship.updated_at # => "2012-09-23 14:24:03"
61
+ ```
62
+
63
+ If you want to find the specific friendship with somebody and edit/save the friendship, you can do it by using `#write_attribute` and `#save`:
64
+
65
+ ```ruby
66
+ friendship = user.friendship_with(another_user)
67
+ friendship.write_attribute(:close_friend, true)
68
+ friendship.save
69
+
70
+ friendship.close_friend # => true
71
+ friendship.meeting_place # => "Red Rock"
72
+ friendship.created_at # => "2012-09-23 14:24:03"
73
+ friendship.updated_at # => "2012-09-23 14:25:21"
74
+ ```
75
+
76
+ **Note:** properties are not saved on the opposite direction of the friendship.
77
+
78
+ ```ruby
79
+ user.friend!(another_user, :meeting_place => "Red Rock")
80
+
81
+ friendship = user.friendship_with(another_user)
82
+ friendship.meeting_place # => "Red Rock"
83
+
84
+ friendship = another_user.friendship_with(user) # opposite
85
+ friendship.meeting_place # => NoMethodError
86
+ ```
87
+
88
+ ### Removing a friend
89
+
90
+ You can remove a user from the friend list of a user like this:
91
+
92
+ ```ruby
93
+ user.unfriend!(another_user)
94
+
95
+ user.friend?(another_user) # => false
96
+ user.friends.include?(another_user) # => false
97
+ user.friend_ids.include?(another_user.id) # => false
98
+ ```
99
+
100
+ For more details, have a look at the documentation.
101
+
102
+ ## Support
103
+
104
+ * Ruby version: 1.9.3, 1.9.2, 1.8.7 and REE
105
+ * ORM: ActiveRecord
106
+
107
+ You also(and of course) need to install Redis.
108
+
109
+ ## Contributing
110
+
111
+ 1. Fork it
112
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
113
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
114
+ 4. Push to the branch (`git push origin my-new-feature`)
115
+ 5. Create new Pull Request
116
+
117
+ ## Copyright
118
+ Copyright (c) 2012 Yuki Nishijima. See LICENSE.md for further details.
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
@@ -0,0 +1,26 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/friendable/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Yuki Nishijima"]
6
+ gem.email = ["mail@yukinishijima.net"]
7
+ gem.description = %q{Redis backed friendship engine for your Ruby models}
8
+ gem.summary = %q{Redis backed friendship engine for your Ruby models}
9
+ gem.homepage = "https://github.com/yuki24/friendable"
10
+
11
+ gem.files = `git ls-files`.split($\)
12
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
13
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
14
+ gem.name = "friendable"
15
+ gem.require_paths = ["lib"]
16
+ gem.version = Friendable::VERSION
17
+
18
+ gem.add_dependency 'msgpack'
19
+ gem.add_dependency 'activesupport', '>= 3.0.0'
20
+ gem.add_dependency 'redis-namespace'
21
+ gem.add_dependency 'keytar'
22
+
23
+ gem.add_development_dependency 'rspec'
24
+ gem.add_development_dependency 'activerecord'
25
+ gem.add_development_dependency 'sqlite3'
26
+ end
@@ -0,0 +1,41 @@
1
+ require "friendable/version"
2
+ require "friendable/user_methods"
3
+ require "friendable/friendship"
4
+ require "friendable/exceptions"
5
+ require "redis-namespace"
6
+
7
+ module Friendable
8
+ extend self
9
+ attr_accessor :resource_class
10
+
11
+ def redis
12
+ @redis ||= Redis::Namespace.new(:friendable, :redis => Redis.new)
13
+ end
14
+
15
+ # Accepts:
16
+ # 1. A 'hostname:port' String
17
+ # 2. A 'hostname:port:db' String (to select the Redis db)
18
+ # 3. A 'hostname:port/namespace' String (to set the Redis namespace)
19
+ # 4. A Redis URL String 'redis://host:port'
20
+ # 5. An instance of `Redis`, `Redis::Client`, `Redis::DistRedis`,
21
+ # or `Redis::Namespace`.
22
+ def redis=(server)
23
+ case server
24
+ when String
25
+ if server =~ /redis\:\/\//
26
+ redis = Redis.connect(:url => server, :thread_safe => true)
27
+ else
28
+ server, namespace = server.split('/', 2)
29
+ host, port, db = server.split(':')
30
+ redis = Redis.new(:host => host, :port => port, :thread_safe => true, :db => db)
31
+ end
32
+ namespace ||= :friendable
33
+
34
+ @redis = Redis::Namespace.new(namespace, :redis => redis)
35
+ when Redis::Namespace
36
+ @redis = server
37
+ else
38
+ @redis = Redis::Namespace.new(:friendable, :redis => server)
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,6 @@
1
+ module Friendable
2
+ # A general Friendable exception
3
+ class Error < StandardError; end
4
+
5
+ class FriendshipNotFound < Error; end
6
+ end
@@ -0,0 +1,70 @@
1
+ require 'msgpack'
2
+
3
+ module Friendable
4
+ class Friendship
5
+ attr_accessor :target_resource, :source_resource
6
+ alias :friend :target_resource
7
+
8
+ def initialize(source_resource, target_resource, attrs = {})
9
+ # options = attrs.delete(:options) if attrs[:options]
10
+ @source_resource = source_resource
11
+ @target_resource = target_resource
12
+ @attributes = attrs.reverse_merge!(:created_at => nil, :updated_at => nil).symbolize_keys
13
+ end
14
+
15
+ def write_attribute(attr_name, value)
16
+ @attributes[attr_name.to_sym] = value
17
+ end
18
+
19
+ def save
20
+ write_attribute(:created_at, Time.zone.now) unless @attributes[:created_at]
21
+ write_attribute(:updated_at, Time.zone.now)
22
+
23
+ Friendable.redis.hset(redis_key, target_resource.id, self.to_msgpack)
24
+ end
25
+
26
+ def to_msgpack
27
+ serializable.to_msgpack
28
+ end
29
+
30
+ # Get a pretty string representation of the friendship, including the
31
+ # user who is a friend with and attributes for inspection.
32
+ #
33
+ def inspect
34
+ "#<Friendable::Friendship with: #{@target_resource.class}(id: #{@target_resource.id}), " <<
35
+ "attributes: " <<
36
+ @attributes.to_a.map{|ary| ary.join(": ") }.join(", ") << ">"
37
+ end
38
+
39
+ def self.deserialize!(source_resource, target_resource, options)
40
+ attrs = MessagePack.unpack(options).symbolize_keys
41
+ attrs[:created_at] = Time.zone.at(attrs[:created_at])
42
+ attrs[:updated_at] = Time.zone.at(attrs[:updated_at])
43
+
44
+ Friendship.new(source_resource, target_resource, attrs)
45
+ end
46
+
47
+ private
48
+
49
+ def method_missing(method, *args, &block)
50
+ if method.to_s.last == "=" && @attributes.has_key?(method.to_s[0..-2].to_sym)
51
+ write_attribute(method.to_s[0..-2], args.first)
52
+ elsif @attributes.has_key?(method)
53
+ return @attributes[method]
54
+ else
55
+ super(method, args, block)
56
+ end
57
+ end
58
+
59
+ def redis_key
60
+ source_resource.friend_list_key
61
+ end
62
+
63
+ def serializable
64
+ @attributes.clone.tap do |serializable_object|
65
+ serializable_object[:created_at] = serializable_object[:created_at].try(:utc).try(:to_i)
66
+ serializable_object[:updated_at] = serializable_object[:updated_at].try(:utc).try(:to_i)
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,153 @@
1
+ require 'active_support/concern'
2
+ require 'keytar'
3
+
4
+ module Friendable
5
+ module UserMethods
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ Friendable.resource_class = self
10
+ include Keytar
11
+ define_key :friend_list, :key_case => nil
12
+ end
13
+
14
+ # Returns an array object of the friend ids of your friends.
15
+ #
16
+ # ==== Examples
17
+ # current_user.friend!(target_user)
18
+ #
19
+ # current_user.friend_ids.include?(target_user.id) # => true
20
+ #
21
+ def friend_ids
22
+ @_friend_ids ||= (@_raw_friend_hashes ? @_raw_friend_hashes.keys : raw_friend_ids).map(&:to_i)
23
+ end
24
+
25
+ # Returns an collection of user ressources(ActiveRecord::Relation).
26
+ #
27
+ # ==== Examples
28
+ # current_user.friends # => [#<User id: 1, ...>, #<User id: 2, ...>, ...]
29
+ # current_user.friends.first # => #<User id: 1, ...>
30
+ # current_user.friends.class # => ActiveRecord::Relation
31
+ #
32
+ def friends
33
+ @_friends ||= Friendable.resource_class.where(:id => friend_ids)
34
+ end
35
+
36
+ # Returns an array of friendship objects. Currently this method does not support
37
+ # pagination.
38
+ #
39
+ # ==== Examples
40
+ # current_user.friendships # => [#<Friendable::Friendship>, ...]
41
+ # current_user.friendships.first # => #<Friendable::Friendship>
42
+ # current_user.friendships.class # => Array
43
+ #
44
+ def friendships
45
+ @_friendships ||= Friendable.resource_class.find(raw_friend_hashes.keys).map do |resource|
46
+ Friendship.deserialize!(self, resource, raw_friend_hashes[resource.id.to_s])
47
+ end
48
+ end
49
+
50
+ # Returns a friendship object that indicates the relation from a user to another user.
51
+ #
52
+ # ==== Examples
53
+ # current_user.friend!(target_user, :foo => "bar")
54
+ #
55
+ # friendship = current_user.friendship_with(target_user)
56
+ # friendship.friend == target_user # => true
57
+ # friendship.foo # => "bar"
58
+ # friendship.bar # => NoMethodError
59
+ #
60
+ def friendship_with(target_resource)
61
+ raw_friendship = @_raw_friend_hashes.try(:[], target_resource.id.to_s) || redis.hget(friend_list_key, target_resource.id)
62
+
63
+ if raw_friendship
64
+ Friendship.deserialize!(self, target_resource, raw_friendship)
65
+ else
66
+ raise Friendable::FriendshipNotFound, "user:#{self.id} is not a friend of the user:#{target_resource.id}"
67
+ end
68
+ end
69
+
70
+ # Returns the number of the user's friends.
71
+ #
72
+ # ==== Examples
73
+ # current_user.friends_count # => 0
74
+ #
75
+ # current_user.friend!(target_user)
76
+ # current_user.friends_count # => 1
77
+ #
78
+ def friends_count
79
+ @_raw_friend_ids.try(:count) || @_raw_friend_hashes.try(:count) || redis.hlen(friend_list_key)
80
+ end
81
+
82
+ # Returns if the given user is friend of you or not.
83
+ #
84
+ # ==== Examlpes
85
+ # current_user.friend?(target_user) # => false
86
+ #
87
+ # current_user.friend!(target_user)
88
+ #
89
+ # current_user.friend?(target_user) # => true
90
+ #
91
+ def friend?(resource)
92
+ @_raw_friend_ids.try(:include?, resource.id.to_s) || @_raw_friend_hashes.try(:has_key?, resource.id.to_s) || redis.hexists(friend_list_key, resource.id)
93
+ end
94
+
95
+ # Adds the given user to your friend list. If the given user is already
96
+ # a friend of you and different options are given, the existing options
97
+ # will be replaced with the new options. It returns an object of
98
+ # Friendable::Friendship.
99
+ #
100
+ # ==== Examples
101
+ # current_user.friends.include?(target_user) # => false
102
+ # current_user.friend!(target_user) # => #<Friendable::Friendship>
103
+ # current_user.friends.include?(target_user) # => true
104
+ #
105
+ # friendship = current_user.friend!(target_user, :foo => "bar")
106
+ # friendship.foo # => "bar"
107
+ # friendship.bar # => NoMethodError
108
+ #
109
+ def friend!(resource, options = {})
110
+ # TODO: in the near future, I want to change to something like this:
111
+ # redis.multi do
112
+ # Friendship.new(self, resource, options).save(inverse: true)
113
+ # end
114
+ Friendship.new(self, resource, options).tap do |friendship|
115
+ redis.multi do
116
+ friendship.save
117
+ Friendship.new(resource, self).save
118
+ end
119
+ end
120
+ end
121
+
122
+ # Removes the given user from your friend list. If the given user is not
123
+ # a friend of you, it doesn't affect anything.
124
+ #
125
+ # ==== Examples
126
+ # current_user.friend!(target_user)
127
+ # current_user.friends.include?(target_user) # => true
128
+ #
129
+ # current_user.unfriend!(target_user)
130
+ # current_user.friends.include?(target_user) # => false
131
+ #
132
+ def unfriend!(resource)
133
+ redis.multi do
134
+ redis.hdel(friend_list_key, resource.id)
135
+ redis.hdel(resource.friend_list_key, self.id)
136
+ end
137
+ end
138
+
139
+ private
140
+
141
+ def raw_friend_hashes
142
+ @_raw_friend_hashes ||= redis.hgetall(friend_list_key)
143
+ end
144
+
145
+ def raw_friend_ids
146
+ @_raw_friend_ids ||= redis.hkeys(friend_list_key)
147
+ end
148
+
149
+ def redis
150
+ @_redis ||= Friendable.redis
151
+ end
152
+ end
153
+ end
@@ -0,0 +1,3 @@
1
+ module Friendable
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,114 @@
1
+ require 'spec_helper'
2
+
3
+ # Suppose the following class exists:
4
+ #
5
+ # class User < ActiveRecord::Base
6
+ # include Friendable::UserMethods
7
+ # end
8
+
9
+ describe Friendable::Friendship do
10
+ def redis; Friendable.redis; end
11
+ before(:each) { redis.flushdb }
12
+ let(:current_user) { User.first }
13
+ let(:target_user) { User.last }
14
+
15
+ describe "initialization" do
16
+ context "without options" do
17
+ subject { Friendable::Friendship.new(current_user, target_user) }
18
+ its(:created_at) { should be_nil }
19
+ its(:updated_at) { should be_nil }
20
+ end
21
+ end
22
+
23
+ describe "dynamic accessors" do
24
+ let(:friendship) { Friendable::Friendship.new(current_user, target_user, :foo => "bar", :hoge => "fuga") }
25
+ subject { friendship }
26
+ its(:foo) { should == "bar" }
27
+ its(:hoge) { should == "fuga" }
28
+
29
+ it "should not allow to dynamically set a new value" do
30
+ expect { friendship.new_attr = "new value" }.to raise_exception(NoMethodError)
31
+ end
32
+
33
+ describe "#write_attribute" do
34
+ specify { friendship.write_attribute(:another_attr, "another value").should == "another value" }
35
+
36
+ context "after the definition" do
37
+ before { friendship.write_attribute(:another_attr, "another value") }
38
+ its(:another_attr) { should == "another value" }
39
+ specify do
40
+ friendship.another_attr = "new value"
41
+ friendship.another_attr.should == "new value"
42
+ end
43
+ end
44
+ end
45
+ =begin
46
+ describe "#remove_attribute" do
47
+ specify { friendship.remove_attribute(:foo).should == "bar" }
48
+
49
+ context "after the removal" do
50
+ before { friendship.remove_attribute(:foo) }
51
+
52
+ it "can no longer get a new value from foo" do
53
+ expect { friendship.foo }.to raise_exception(NoMethodError)
54
+ end
55
+
56
+ it "can no longer set a new value to foo" do
57
+ expect { friendship.foo = "bar" }.to raise_exception(NoMethodError)
58
+ end
59
+ end
60
+ end
61
+ =end
62
+ end
63
+
64
+ describe "serialization" do
65
+ context "with options" do
66
+ let(:friendship) { Friendable::Friendship.new(current_user, target_user, :foo => "bar", :hoge => "fuga") }
67
+ specify do
68
+ MessagePack.unpack(friendship.to_msgpack).should == {
69
+ "foo" => "bar",
70
+ "hoge" => "fuga",
71
+ "created_at" => nil,
72
+ "updated_at" => nil
73
+ }
74
+ end
75
+ end
76
+ end
77
+
78
+ describe "deserialization" do
79
+ let(:current_timestamp) { Time.now.to_i }
80
+ let(:msgpacked_data) { {:created_at => current_timestamp, :updated_at => current_timestamp}.to_msgpack }
81
+
82
+ context "with options" do
83
+ subject { Friendable::Friendship.deserialize!(current_user, target_user, msgpacked_data) }
84
+ it { should be_a(Friendable::Friendship) }
85
+ its(:source_resource) { should == current_user }
86
+ its(:target_resource) { should == target_user }
87
+ its(:created_at) { should == Time.zone.at(current_timestamp) }
88
+ its(:updated_at) { should == Time.zone.at(current_timestamp) }
89
+ end
90
+ end
91
+
92
+ describe "#save" do
93
+ before { current_user.friend!(target_user) }
94
+ let(:friendship) { current_user.friendship_with(target_user) }
95
+
96
+ it "should persist the newly assigned value" do
97
+ created_at = friendship.created_at
98
+ updated_at = friendship.updated_at
99
+ sleep 1
100
+ friendship.save
101
+
102
+ friendship = current_user.friendship_with(target_user)
103
+ friendship.created_at.to_i.should == created_at.to_i
104
+ friendship.updated_at.to_i.should_not == updated_at.to_i
105
+ end
106
+ end
107
+
108
+ describe "#inspect" do
109
+ subject { Friendable::Friendship.new(current_user, target_user, :foo => "bar", :hoge => "fuga") }
110
+ its(:inspect) { should ==
111
+ "#<Friendable::Friendship with: User(id: #{target_user.id}), attributes: foo: bar, hoge: fuga, created_at: , updated_at: >"
112
+ }
113
+ end
114
+ end
@@ -0,0 +1,153 @@
1
+ require 'spec_helper'
2
+
3
+ # Suppose the following class exists:
4
+ #
5
+ # class User < ActiveRecord::Base
6
+ # include Friendable::UserMethods
7
+ # end
8
+
9
+ describe Friendable::UserMethods do
10
+ def redis; Friendable.redis; end
11
+ before(:each) { redis.flushdb }
12
+ let(:current_user) { User.first }
13
+ let(:target_user) { User.last }
14
+
15
+ subject { current_user }
16
+ its(:friend_list_key) { should == "Users:friend_list:1" }
17
+
18
+ describe "#friend!" do
19
+ context "to add a friend" do
20
+ before { current_user.friend!(target_user) }
21
+ specify { current_user.friend?(target_user).should be_true }
22
+ specify { redis.hkeys(current_user.friend_list_key).should include(target_user.id.to_s) }
23
+ specify { redis.hkeys(target_user.friend_list_key).should include(current_user.id.to_s) }
24
+ end
25
+
26
+ context "to add a friend with several options" do
27
+ let(:friendship) { current_user.friend!(target_user, :foo => "bar", :hoge => "fuga") }
28
+ subject { friendship }
29
+ it { should be_a(Friendable::Friendship) }
30
+ its(:friend){ should == target_user }
31
+ its(:foo){ should == "bar" }
32
+ its(:hoge){ should == "fuga" }
33
+ end
34
+
35
+ context "to add a friend without options" do
36
+ let(:friendship) { current_user.friend!(target_user) }
37
+ subject { friendship }
38
+ it { should be_a(Friendable::Friendship) }
39
+ its(:friend){ should == target_user }
40
+ end
41
+ end
42
+
43
+ describe "#unfriend!" do
44
+ context "to remove a friendship" do
45
+ before do
46
+ current_user.friend!(target_user)
47
+ current_user.unfriend!(target_user)
48
+ end
49
+
50
+ specify { current_user.friend?(target_user).should be_false }
51
+ specify { redis.hexists(current_user.friend_list_key, target_user.id.to_s).should be_false }
52
+ specify { redis.hexists(target_user.friend_list_key, current_user.id.to_s).should be_false }
53
+ end
54
+ end
55
+
56
+ describe "#friends" do
57
+ subject { current_user.friends }
58
+
59
+ context "without friends" do
60
+ it { should be_an(ActiveRecord::Relation) }
61
+ its(:count){ should == 0 }
62
+ end
63
+
64
+ context "with one friend" do
65
+ before { current_user.friend!(target_user) }
66
+ it { should include(target_user) }
67
+ end
68
+ end
69
+
70
+ describe "#friendships" do
71
+ subject { current_user.friendships }
72
+
73
+ context "without friends to return an empty erray" do
74
+ it { should be_an(Array) }
75
+ its(:count) { should == 0 }
76
+ end
77
+
78
+ context "with one friend to return an array of friendship objects" do
79
+ before { current_user.friend!(target_user, :foo => "bar", :hoge => "fuga") }
80
+ subject { current_user.friendships.first }
81
+ it { should be_a(Friendable::Friendship) }
82
+ its(:source_resource) { should == current_user }
83
+ its(:target_resource) { should == target_user }
84
+ its(:created_at) { should be_a(ActiveSupport::TimeWithZone) }
85
+ its(:updated_at) { should be_a(ActiveSupport::TimeWithZone) }
86
+ its(:foo) { should == "bar" }
87
+ its(:hoge) { should == "fuga" }
88
+ end
89
+ end
90
+
91
+ describe "#friendship_with" do
92
+ context "without friends" do
93
+ specify do
94
+ expect {
95
+ current_user.friendship_with(target_user)
96
+ }.to raise_exception(Friendable::FriendshipNotFound)
97
+ end
98
+ end
99
+
100
+ context "with one friend" do
101
+ before { current_user.friend!(target_user, :foo => "bar", :hoge => "fuga") }
102
+ subject { current_user.friendship_with(target_user) }
103
+ it { should be_a(Friendable::Friendship) }
104
+ its(:foo) { should == "bar" }
105
+ its(:hoge) { should == "fuga" }
106
+ end
107
+ end
108
+
109
+ describe "#friend_ids" do
110
+ subject { current_user.friend_ids }
111
+
112
+ context "without friends" do
113
+ it { should be_an(Array) }
114
+ its(:count) { should == 0 }
115
+ end
116
+
117
+ context "with one friend" do
118
+ before { current_user.friend!(target_user) }
119
+ it { should include(target_user.id) }
120
+ end
121
+
122
+ context "without friends after unfriend" do
123
+ before do
124
+ current_user.friend!(target_user)
125
+ current_user.unfriend!(target_user)
126
+ end
127
+
128
+ it { should_not include(target_user.id) }
129
+ end
130
+ end
131
+
132
+ describe "#friends_count" do
133
+ subject { current_user.friends_count }
134
+
135
+ context "without friends" do
136
+ it { should == 0 }
137
+ end
138
+
139
+ context "with one friend" do
140
+ before { current_user.friend!(target_user) }
141
+ it { should == 1 }
142
+ end
143
+
144
+ context "without friends after unfriend" do
145
+ before do
146
+ current_user.friend!(target_user)
147
+ current_user.unfriend!(target_user)
148
+ end
149
+
150
+ it { should == 0 }
151
+ end
152
+ end
153
+ end
@@ -0,0 +1,42 @@
1
+ require 'spec_helper'
2
+
3
+ describe Friendable do
4
+ it { should respond_to :redis }
5
+ it { should respond_to :resource_class }
6
+ it { should respond_to :resource_class= }
7
+ its(:redis) { should be_a(Redis::Namespace) }
8
+
9
+ describe "confgiruration" do
10
+ context "with the default redis" do
11
+ subject { Friendable.redis.client }
12
+ it { should be_a(Redis::Client) }
13
+ its(:host) { should == "127.0.0.1" }
14
+ its(:port) { should == 6379 }
15
+ end
16
+
17
+ context "with a string" do
18
+ before { Friendable.redis = "10.0.0.1:6379:1" }
19
+ subject { Friendable.redis.client }
20
+ it { should be_a(Redis::Client) }
21
+ its(:host) { should == "10.0.0.1" }
22
+ its(:port) { should == 6379 }
23
+ its(:db) { should == 1 }
24
+ end
25
+
26
+ context "with an object of Redis class" do
27
+ before { Friendable.redis = Redis.new(:host => "127.0.0.1", :port => 6379) }
28
+ subject { Friendable.redis.client }
29
+ it { should be_a(Redis::Client) }
30
+ its(:host) { should == "127.0.0.1" }
31
+ its(:port) { should == 6379 }
32
+ end
33
+
34
+ context "with an object of Redis::Namespace class" do
35
+ before { Friendable.redis = Redis::Namespace.new(:friendable, :host => "127.0.0.1", :port => 6379) }
36
+ subject { Friendable.redis.client }
37
+ it { should be_a(Redis::Client) }
38
+ its(:host) { should == "127.0.0.1" }
39
+ its(:port) { should == 6379 }
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,39 @@
1
+ require 'simplecov'
2
+
3
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
4
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
5
+
6
+ require 'bundler/setup'
7
+ Bundler.require
8
+
9
+ # database
10
+ require 'active_record'
11
+ ActiveRecord::Base.configurations = {'test' => {:adapter => 'sqlite3', :database => ':memory:'}}
12
+ ActiveRecord::Base.establish_connection('test')
13
+
14
+ # migrations
15
+ class CreateAllTables < ActiveRecord::Migration
16
+ def self.up
17
+ create_table(:users) {|t| t.string :name; t.integer :age}
18
+ end
19
+ end
20
+ ActiveRecord::Migration.verbose = false
21
+ CreateAllTables.up
22
+
23
+ # models
24
+ class User < ActiveRecord::Base
25
+ include Friendable::UserMethods
26
+ end
27
+ User.create!(:name => "Some User")
28
+ User.create!(:name => "Another User")
29
+
30
+ # default time sone
31
+ Time.zone = "UTC"
32
+
33
+ # Requires supporting files with custom matchers and macros, etc,
34
+ # in ./support/ and its subdirectories.
35
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f }
36
+
37
+ RSpec.configure do |config|
38
+
39
+ end
metadata ADDED
@@ -0,0 +1,180 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: friendable
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Yuki Nishijima
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-09-26 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: msgpack
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: activesupport
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: 3.0.0
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: 3.0.0
46
+ - !ruby/object:Gem::Dependency
47
+ name: redis-namespace
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: keytar
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ type: :runtime
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ - !ruby/object:Gem::Dependency
79
+ name: rspec
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ! '>='
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ type: :development
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ - !ruby/object:Gem::Dependency
95
+ name: activerecord
96
+ requirement: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ! '>='
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ type: :development
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ! '>='
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ - !ruby/object:Gem::Dependency
111
+ name: sqlite3
112
+ requirement: !ruby/object:Gem::Requirement
113
+ none: false
114
+ requirements:
115
+ - - ! '>='
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ none: false
122
+ requirements:
123
+ - - ! '>='
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
126
+ description: Redis backed friendship engine for your Ruby models
127
+ email:
128
+ - mail@yukinishijima.net
129
+ executables: []
130
+ extensions: []
131
+ extra_rdoc_files: []
132
+ files:
133
+ - .gitignore
134
+ - .rspec
135
+ - .rvmrc
136
+ - .simplecov
137
+ - .travis.yml
138
+ - Gemfile
139
+ - LICENSE.md
140
+ - README.md
141
+ - Rakefile
142
+ - friendable.gemspec
143
+ - lib/friendable.rb
144
+ - lib/friendable/exceptions.rb
145
+ - lib/friendable/friendship.rb
146
+ - lib/friendable/user_methods.rb
147
+ - lib/friendable/version.rb
148
+ - spec/friendable/friendship_spec.rb
149
+ - spec/friendable/user_methods_spec.rb
150
+ - spec/friendable_spec.rb
151
+ - spec/spec_helper.rb
152
+ homepage: https://github.com/yuki24/friendable
153
+ licenses: []
154
+ post_install_message:
155
+ rdoc_options: []
156
+ require_paths:
157
+ - lib
158
+ required_ruby_version: !ruby/object:Gem::Requirement
159
+ none: false
160
+ requirements:
161
+ - - ! '>='
162
+ - !ruby/object:Gem::Version
163
+ version: '0'
164
+ required_rubygems_version: !ruby/object:Gem::Requirement
165
+ none: false
166
+ requirements:
167
+ - - ! '>='
168
+ - !ruby/object:Gem::Version
169
+ version: '0'
170
+ requirements: []
171
+ rubyforge_project:
172
+ rubygems_version: 1.8.24
173
+ signing_key:
174
+ specification_version: 3
175
+ summary: Redis backed friendship engine for your Ruby models
176
+ test_files:
177
+ - spec/friendable/friendship_spec.rb
178
+ - spec/friendable/user_methods_spec.rb
179
+ - spec/friendable_spec.rb
180
+ - spec/spec_helper.rb