hari 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,15 +1,7 @@
1
1
  ---
2
- !binary "U0hBMQ==":
3
- metadata.gz: !binary |-
4
- MDFjMThhM2VkNGMwNTRlYTMzNDM2Y2UyN2E5MDZhOTYxZDViZWI3MQ==
5
- data.tar.gz: !binary |-
6
- NTM0ZDBmNmJkYjMwOGZiNGRiZWE0MDg3NjYzZjIzZjBlNWY3YWI2Nw==
7
- !binary "U0hBNTEy":
8
- metadata.gz: !binary |-
9
- MTA3OTM4MTkxN2E3MWM0YjRkNjBkYjNhNzc1OWIxMjZhMzU2NTJlYmZlNjRl
10
- NmZmOTZkZjY3Yzk4ZTY3Zjk3NzRiMjJkMjdlMTNjNDgyZGFiZGQ1ZGY2YmQw
11
- ZmZiN2JkZmEzYzkwZDI3Y2M4YzQ1NDlmZTExOTM0ODY0N2NlMDQ=
12
- data.tar.gz: !binary |-
13
- ZDBlNjJiYTg3NWRiMzU5YTkzNDRiNzRlOGE3MDFhYzI3ZDI0YWI4M2UzZDkx
14
- NjljYTE1ODUwZjlkODIxZjc5Y2E4N2Q1ZjU0OGYxMTM4MDgxYzRlMTZiZDEw
15
- ZWY2ZDIyNmIwYjU5NjRjMDI5ZjQyYWM5MTZhZDQ2YmJiZWQxNDg=
2
+ SHA1:
3
+ metadata.gz: 6bec03d308713945bce8a8b110c663beb57450f7
4
+ data.tar.gz: 4679c39080577b390539c16921264c6d69ce0b50
5
+ SHA512:
6
+ metadata.gz: 8d1b9c6f45f02be08c99b3f01286031d7c94a64a91d9f14c904acfd9934e0c3c9fa986cdf05a77b8af93d4e37ba25d2d8837d59fc1082cf112c315229a306ab3
7
+ data.tar.gz: 984b40a10bc769cb874b6879bfd10de44b970225480ed2d73285d6536ef5aed4f148d114721e160c7a79fc6ecd73646fc7e9f42edfe72c47a740579d35c9f139
data/LICENSE CHANGED
@@ -1,18 +1,20 @@
1
- Copyright (c) 2013, Clubjudge
2
- All rights reserved.
1
+ The MIT License (MIT)
3
2
 
4
- Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
3
+ Copyright (c) 2013 ClubJudge B.V.
5
4
 
6
- - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
7
- - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the docume
8
- ntation and/or other materials provided with the distribution.
9
- - Neither the name of the Clubjudge nor the names of its contributors may be used to endorse or promote products derived from this
10
- software without specific prior written permission.
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ this software and associated documentation files (the "Software"), to deal in
7
+ the Software without restriction, including without limitation the rights to
8
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9
+ the Software, and to permit persons to whom the Software is furnished to do so,
10
+ subject to the following conditions:
11
11
 
12
- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUD
13
- ING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN N
14
- O EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR C
15
- ONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR P
16
- ROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
17
- TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBI
18
- LITY OF SUCH DAMAGE.
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md CHANGED
@@ -1,22 +1,142 @@
1
1
  [![Build Status](https://travis-ci.org/Clubjudge/hari.png?branch=master)](https://travis-ci.org/Clubjudge/hari)
2
2
 
3
- ## hari
3
+ # Hari
4
4
 
5
- Hari is a library to persist and get nodes and its relations in Redis, using different data structures depending on your need.
5
+ ## Mile-high view
6
6
 
7
- Hari(user: 23).out(:follow).out(:activity).limit(25)
7
+ **Hari** is a tool to abstract complex relationships between **Ruby** objects onto **Redis** data structures. It allows for expressive querying of those relationships as well, in an easy way. It is mostly geared towards typical social networking concepts like news feeds, activity logs, friends of friends, mutual friends, and so on.
8
8
 
9
- The query above will return the top `25` activities from all nodes user `23` follows.
9
+ ## Basic concepts
10
10
 
11
- ### Relations
11
+ Hari embraces normal objects, and allows 2 major modes of operation: abstraction of Redis operations, and actual relationship creation and querying.
12
+
13
+ ## Abstraction of Redis operations (lists, sets, sorted sets, etc)
14
+
15
+ Imagine this `User` model class:
16
+
17
+ ```ruby
18
+ class User
19
+
20
+ attr_reader :id
21
+
22
+ def initialize(id)
23
+ @id = id
24
+ end
25
+
26
+ end
27
+ ```
28
+
29
+ You can create a **set** to store the user relation with his friends:
30
+
31
+ ```ruby
32
+ user = User.new(20)
33
+
34
+ Hari(user).set(:friends_ids) << 10 # REDIS: SADD hari:user#20:friends_ids 10
35
+ ```
36
+
37
+ Then it's possible, for instance, to query the mutual friends between users:
38
+
39
+ ```ruby
40
+ Hari(user: 20).set(:friends_ids) & Hari(user: 30).set(:friends_ids)
41
+
42
+ => ["10", "25", "40"]
43
+ ```
44
+
45
+ By now, you're probably wandering what the `Hari()` method does. It accepts an object like `user` as a parameter, or an identification of this object like `"user#20"`, or even `{user: 30}` and returns a `Hari::Node` representation of the object referenced so you can call all operations available in Hari.
46
+
47
+ ### [Lists](https://github.com/Clubjudge/hari/wiki/Lists), [Sets](https://github.com/Clubjudge/hari/wiki/Sets) and [Sorted Sets](https://github.com/Clubjudge/hari/wiki/Sorted-Sets) operations are available in the Wiki.
48
+
49
+ ## Relationships
50
+
51
+ Hari uses the power of Redis data structures to create relations between objects, allowing you to traverse nodes and its relations like a graph.
52
+
53
+ ```ruby
54
+ # this gets the last 20 comments from the entities the user#1 follows
55
+ Hari(user: 1).out(:follow).out(:comments).limit(20)
56
+ ```
12
57
 
13
58
  Creating a relation can be as simple as:
14
59
 
15
- Hari.relation! :follow, user, event
60
+ ```ruby
61
+ Hari.relation! :follow, user, artist
62
+ ```
63
+
64
+ To remove a relation, do:
65
+
66
+ ```ruby
67
+ Hari.remove_relation! :follow, user, artist
68
+ ```
69
+
70
+ Let's mount some queries:
71
+
72
+ ```ruby
73
+ # artist followers
74
+ Hari(artist).in(:follow)
75
+
76
+ # entities that user follow
77
+ Hari(user).out(:follow)
78
+
79
+ # just the last 10 followers of artist
80
+ Hari(artist).in(:follow).limit(10)
81
+
82
+ # paginates from a score (timestamp.to_f),
83
+ # bringing the next 10 followers up from a last timestamp,
84
+ # (useful for pooling streams)
85
+ Hari(artist).in(:follow).limit(10).from(1375977470.382)
86
+
87
+ # paginates from a score down,
88
+ # bringing the previous 10 followers from a last timestamp
89
+ Hari(artist).in(:follow).limit(10).from(1375977470.382, :down)
90
+
91
+ # chaining relations between nodes
92
+ # last 10 entities to be followed by who user follows
93
+ Hari(user).out(:follow).out(:follow).limit(10)
94
+
95
+ # All users following artist
96
+ Hari(artist).in(:follow).type(:user)
97
+ ```
98
+
99
+ All the calls above return a lazy query expression. The ruby code still didn't fetch the Redis backend, it's mounting a composable query.
100
+
101
+ Below there are some of the methods that make the query come to an end:
102
+
103
+ ```ruby
104
+ # how many followers
105
+ Hari(artist).in(:follow).count
106
+ => 2001
107
+
108
+ # returns all followers nodes ids
109
+ Hari(artist).in(:follow).nodes_ids!
110
+ => ['artist#20', 'user#30', 'user#33']
111
+
112
+
113
+ # returns all followers instances of Hari::Node
114
+ # depends that you have the nodes persisted for each object
115
+ # this is the default implementation when you do .to_a
116
+ Hari(artist).in(:follow).nodes!
117
+
118
+ tiesto_followers = Hari(artist: 21).in(:follow).type(:user)
119
+ daftpunk_followers = Hari(artist: 42).in(:follow).type(:user)
120
+
121
+ # count of common users following two artists
122
+ tiesto_followers.intersect_count(daft_punk_followers)
123
+ => 6
124
+
125
+ # actual users ids following two artists
126
+ tiesto_followers.intersect(daft_punk_followers)
127
+ => [17, 29, 3, 173, 919, 11]
128
+
129
+ # paginating through them (start + stop)
130
+ tiesto_followers.intersect(daft_punk_followers, 2, 5)
131
+ => [3, 173, 919]
132
+
133
+ user_friends = Hari(user).out(:follow).type(:user)
16
134
 
17
- # where user / event are:
135
+ # bringing all followers of artist, but user's friends first
136
+ tiesto_followers.sort_by user_friends
137
+ => [883, 317, 211, 157, 163, 103, 47, 53, 7]
18
138
 
19
- - objects with #id method (node representation will be class#id, like user#23)
20
- - strings with node_type#node_id
21
- - hash like { node_type => node_id }
22
- - a Hari::Node instance
139
+ # paginating through them (offset + count)
140
+ tiesto_followers.sort_by user_friends, 3, 5
141
+ => [157, 163, 103, 47, 53]
142
+ ```
@@ -1,8 +1,7 @@
1
- require 'hari/entity/property/builder'
2
-
3
1
  module Hari
4
2
  class Entity
5
3
  class Property
4
+ autoload :Builder, 'hari/entity/property/builder'
6
5
 
7
6
  attr_accessor :name, :serializer, :options
8
7
 
@@ -1,16 +1,16 @@
1
- require 'hari/entity/serialization/boolean'
2
- require 'hari/entity/serialization/date'
3
- require 'hari/entity/serialization/datetime'
4
- require 'hari/entity/serialization/float'
5
- require 'hari/entity/serialization/integer'
6
- require 'hari/entity/serialization/string'
7
- require 'hari/entity/serialization/time'
8
-
9
1
  module Hari
10
2
  class Entity
11
3
  module Serialization
12
4
  extend ActiveSupport::Concern
13
5
 
6
+ autoload :Boolean, 'hari/entity/serialization/boolean'
7
+ autoload :Date, 'hari/entity/serialization/date'
8
+ autoload :DateTime, 'hari/entity/serialization/datetime'
9
+ autoload :Float, 'hari/entity/serialization/float'
10
+ autoload :Integer, 'hari/entity/serialization/integer'
11
+ autoload :String, 'hari/entity/serialization/string'
12
+ autoload :Time, 'hari/entity/serialization/time'
13
+
14
14
  def to_json
15
15
  hash = self.class.properties.inject({}) do |buffer, prop|
16
16
  buffer.merge prop.name => prop.serialize(send(prop.name))
data/lib/hari/entity.rb CHANGED
@@ -7,6 +7,11 @@ module Hari
7
7
  extend ActiveModel::Naming
8
8
  extend ActiveModel::Callbacks
9
9
  include ActiveModel::Validations
10
+
11
+ autoload :Property, 'hari/entity/property'
12
+ autoload :Repository, 'hari/entity/property'
13
+ autoload :Serialization, 'hari/entity/property'
14
+
10
15
  extend Property::Builder
11
16
  include Repository
12
17
  include Serialization
@@ -0,0 +1,48 @@
1
+ module Hari
2
+ module Keys
3
+ class Key
4
+
5
+ attr_reader :node, :name
6
+
7
+ def initialize(node = nil)
8
+ @node = node
9
+ end
10
+
11
+ def key
12
+ @key ||= begin
13
+ prefix = node ? "#{Hari.node_key(node)}:" : ''
14
+ prefix + name.to_s
15
+ end
16
+ end
17
+
18
+ def delete!
19
+ Hari.redis.del key
20
+ end
21
+
22
+ def exists?
23
+ Hari.redis.exists key
24
+ end
25
+
26
+ def expire(milliseconds)
27
+ Hari.redis.pexpire key, milliseconds
28
+ end
29
+
30
+ def expire_at(timestamp)
31
+ Hari.redis.expireat key, ::Time.parse(timestamp).to_s
32
+ end
33
+
34
+ def persist
35
+ Hari.redis.persist key
36
+ end
37
+
38
+ def type
39
+ Hari.redis.type key
40
+ end
41
+
42
+ def ttl
43
+ Hari.redis.ttl key
44
+ end
45
+
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,132 @@
1
+ module Hari
2
+ module Keys
3
+ class List < Key
4
+
5
+ def list(name)
6
+ @name = name
7
+ self
8
+ end
9
+
10
+ def list!(name)
11
+ @name = name
12
+ range
13
+ end
14
+
15
+ def [](*args)
16
+ arg = args.first
17
+
18
+ if args.size == 2
19
+ range *args
20
+ elsif arg.kind_of? Integer
21
+ at arg
22
+ elsif arg.kind_of? Range
23
+ range arg.first, arg.last
24
+ end
25
+ end
26
+
27
+ def first
28
+ self[0]
29
+ end
30
+
31
+ def last
32
+ self[-1]
33
+ end
34
+
35
+ def []=(index, member)
36
+ Hari.redis.lset key, index, member
37
+ end
38
+
39
+ def range(start = 0, stop = -1)
40
+ Hari.redis.lrange key, start, stop
41
+ end
42
+
43
+ alias :members :range
44
+ alias :to_a :range
45
+
46
+ def from(index)
47
+ range index
48
+ end
49
+
50
+ def to(index)
51
+ range 0, index
52
+ end
53
+
54
+ def at(index)
55
+ Hari.redis.lindex key, index
56
+ end
57
+
58
+ alias :index :at
59
+
60
+ def trim(start, stop)
61
+ Hari.redis.ltrim key, start, stop
62
+ end
63
+
64
+ def count
65
+ Hari.redis.llen key
66
+ end
67
+
68
+ alias :size :count
69
+ alias :length :count
70
+
71
+ def empty?
72
+ count == 0
73
+ end
74
+
75
+ def one?
76
+ count == 1
77
+ end
78
+
79
+ def many?
80
+ count > 1
81
+ end
82
+
83
+ def include?(member)
84
+ range.include? member
85
+ end
86
+
87
+ alias :member? :include?
88
+
89
+ def push(*members)
90
+ Hari.redis.rpush key, members
91
+ end
92
+
93
+ alias :rpush :push
94
+ alias :add :push
95
+
96
+ def lpush(*members)
97
+ Hari.redis.lpush key, members
98
+ end
99
+
100
+ def <<(member)
101
+ push member
102
+ end
103
+
104
+ def insert_before(pivot, member)
105
+ Hari.redis.linsert key, :before, pivot, member
106
+ end
107
+
108
+ def insert_after(pivot, member)
109
+ Hari.redis.linsert key, :after, pivot, member
110
+ end
111
+
112
+ alias :insert :insert_after
113
+
114
+ def delete(member, count = 0)
115
+ Hari.redis.lrem key, count, member
116
+ end
117
+
118
+ def pop
119
+ Hari.redis.rpop key
120
+ end
121
+
122
+ alias :rpop :pop
123
+
124
+ def shift
125
+ Hari.redis.lpop key
126
+ end
127
+
128
+ alias :lpop :shift
129
+
130
+ end
131
+ end
132
+ end
@@ -0,0 +1,101 @@
1
+ module Hari
2
+ module Keys
3
+ class Set < Key
4
+
5
+ def set(name)
6
+ @name = name
7
+ self
8
+ end
9
+
10
+ def set!(name)
11
+ @name = name
12
+ members
13
+ end
14
+
15
+ def members
16
+ Hari.redis.smembers key
17
+ end
18
+
19
+ def rand(count = 1)
20
+ Hari.redis.srandmember key, count
21
+ end
22
+
23
+ def count
24
+ Hari.redis.scard key
25
+ end
26
+
27
+ alias :size :count
28
+ alias :length :count
29
+
30
+ def empty?
31
+ count == 0
32
+ end
33
+
34
+ def one?
35
+ count == 1
36
+ end
37
+
38
+ def many?
39
+ count > 1
40
+ end
41
+
42
+ def include?(member)
43
+ Hari.redis.sismember key, member
44
+ end
45
+
46
+ alias :member? :include?
47
+
48
+ def add(*members)
49
+ Hari.redis.sadd key, members
50
+ end
51
+
52
+ def <<(member)
53
+ add member
54
+ end
55
+
56
+ def delete(*members)
57
+ Hari.redis.srem key, members
58
+ end
59
+
60
+ def pop
61
+ Hari.redis.spop key
62
+ end
63
+
64
+ def intersect(*set_queries)
65
+ Hari.redis.sinter key, set_query_keys(set_queries)
66
+ end
67
+
68
+ def &(other_set_query)
69
+ intersect other_set_query
70
+ end
71
+
72
+ def diff(*set_queries)
73
+ Hari.redis.sdiff key, set_query_keys(set_queries)
74
+ end
75
+
76
+ def -(other_set_query)
77
+ diff other_set_query
78
+ end
79
+
80
+ private
81
+
82
+ def set_query_keys(set_queries)
83
+ keys = set_queries.map do |query|
84
+ ensure_set_query! query
85
+ query.key
86
+ end
87
+
88
+ fail 'no query keys' if keys.empty?
89
+
90
+ keys
91
+ end
92
+
93
+ def ensure_set_query!(query)
94
+ unless query.kind_of?(Hari::Keys::Set)
95
+ fail 'not a set query'
96
+ end
97
+ end
98
+
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,115 @@
1
+ module Hari
2
+ module Keys
3
+ class SortedSet < Key
4
+
5
+ def sorted_set(name)
6
+ @name = name
7
+ self
8
+ end
9
+
10
+ def sorted_set!(name)
11
+ @name = name
12
+ members
13
+ end
14
+
15
+ def range(start = 0, stop = -1, options = {})
16
+ return revrange(start, stop, options) if options[:desc]
17
+
18
+ Hari.redis.zrange key, start, stop, options.slice(:with_scores)
19
+ end
20
+
21
+ alias :members :range
22
+
23
+ def range_with_scores
24
+ range 0, -1, with_scores: true
25
+ end
26
+
27
+ def revrange(start = 0, stop = -1, options = {})
28
+ Hari.redis.zrevrange key, start, stop, options.slice(:with_scores)
29
+ end
30
+
31
+ alias :reverse_range :revrange
32
+ alias :desc_range :revrange
33
+
34
+ def revrange_with_scores
35
+ revrange 0, -1, with_scores: true
36
+ end
37
+
38
+ def range_by_score(min, max, options = {})
39
+ return revrange_by_score(min, max, options) if options[:desc]
40
+
41
+ Hari.redis.zrangebyscore key, min, max, options.slice(:with_scores, :limit)
42
+ end
43
+
44
+ def revrange_by_score(min, max, options = {})
45
+ Hari.redis.zrevrangebyscore key, max, min, options.slice(:with_scores, :limit)
46
+ end
47
+
48
+ def rank(member, options = {})
49
+ return revrank(member, options) if options[:desc]
50
+
51
+ Hari.redis.zrank key, member
52
+ end
53
+
54
+ alias :ranking :rank
55
+ alias :position :rank
56
+
57
+ def revrank(member)
58
+ Hari.redis.zrevrank key, member
59
+ end
60
+
61
+ alias :reverse_ranking :revrank
62
+ alias :reverse_position :revrank
63
+
64
+ def count
65
+ Hari.redis.zcard key
66
+ end
67
+
68
+ alias :size :count
69
+ alias :length :count
70
+
71
+ def empty?
72
+ count == 0
73
+ end
74
+
75
+ def one?
76
+ count == 1
77
+ end
78
+
79
+ def many?
80
+ count > 1
81
+ end
82
+
83
+ def include?(member)
84
+ score(member).present?
85
+ end
86
+
87
+ alias :member? :include?
88
+
89
+ def score(member)
90
+ Hari.redis.zscore key, member
91
+ end
92
+
93
+ def add(*score_members)
94
+ Hari.redis.zadd key, score_members.to_a.flatten
95
+ end
96
+
97
+ def <<(*score_members)
98
+ add score_members
99
+ end
100
+
101
+ def delete(*members)
102
+ Hari.redis.zrem key, members
103
+ end
104
+
105
+ def trim_by_rank(start, stop)
106
+ Hari.redis.zremrangebyrank key, start, stop
107
+ end
108
+
109
+ def trim_by_score(min, max)
110
+ Hari.redis.zremrangebyscore key, min, max
111
+ end
112
+
113
+ end
114
+ end
115
+ end