friendable 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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