hari 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,81 @@
1
+ module Hari
2
+ module Keys
3
+ class String < Key
4
+
5
+ def string(name)
6
+ @name = name
7
+ self
8
+ end
9
+
10
+ def string!(name)
11
+ @name = name
12
+ to_s
13
+ end
14
+
15
+ def to_s
16
+ Hari.redis.get key
17
+ end
18
+
19
+ def set(value)
20
+ Hari.redis.set key, value
21
+ end
22
+
23
+ def length
24
+ Hari.redis.strlen key
25
+ end
26
+
27
+ alias :size :length
28
+
29
+ def range(start = nil, stop = nil)
30
+ start ||= 0
31
+ stop ||= -1
32
+ Hari.redis.getrange key, start, stop
33
+ end
34
+
35
+ def at(index)
36
+ Hari.redis.getrange key, index, index
37
+ end
38
+
39
+ def [](*args)
40
+ arg = args.first
41
+
42
+ if args.size == 2
43
+ range *args
44
+ elsif arg.kind_of? Integer
45
+ at arg
46
+ elsif arg.kind_of? Range
47
+ range arg.first, arg.last
48
+ end
49
+ end
50
+
51
+ def <<(value)
52
+ Hari.redis.append key, value
53
+ end
54
+
55
+ def +(i)
56
+ Hari.redis.incrby key, i
57
+ self
58
+ end
59
+
60
+ def -(i)
61
+ Hari.redis.decrby key, i
62
+ self
63
+ end
64
+
65
+ def bitcount(start = nil, stop = nil)
66
+ start ||= 0
67
+ stop ||= -1
68
+ Hari.redis.bitcount key, start, stop
69
+ end
70
+
71
+ def getbit(offset)
72
+ Hari.redis.getbit key, offset
73
+ end
74
+
75
+ def setbit(offset, value)
76
+ Hari.redis.setbit key, offset, value
77
+ end
78
+
79
+ end
80
+ end
81
+ end
data/lib/hari/keys.rb ADDED
@@ -0,0 +1,9 @@
1
+ module Hari
2
+ module Keys
3
+ autoload :Key, 'hari/keys/key'
4
+ autoload :List, 'hari/keys/list'
5
+ autoload :Set, 'hari/keys/set'
6
+ autoload :SortedSet, 'hari/keys/sorted_set'
7
+ autoload :String, 'hari/keys/string'
8
+ end
9
+ end
@@ -10,10 +10,14 @@ class Hari::Node::Queries::Relation
10
10
 
11
11
  def fetch_relations_ids(set, options)
12
12
  from, limit = options.values_at(:from, :limit)
13
+ limit = limit.try(:to_i)
13
14
 
14
- if from.present?
15
- set.range_by_score from, '+inf', desc: true, limit: [0, limit]
15
+ if from.present? && from[:direction] == 'up'
16
+ set.range_by_score from[:score], '+inf', desc: true, limit: [0, limit]
17
+ elsif from.present? && from[:direction] == 'down'
18
+ set.range_by_score '-inf', from[:score], desc: true, limit: [0, limit]
16
19
  else
20
+ limit -= 1 unless limit <= 0
17
21
  set.range from, limit, desc: true
18
22
  end
19
23
  end
@@ -35,7 +39,7 @@ class Hari::Node::Queries::Relation
35
39
  def step(start_node, nodes_ids, options = {})
36
40
  stream = start_node.sorted_set("stream:#{SecureRandom.hex(6)}")
37
41
  direction = options[:direction] == :in ? 0 : 2
38
- limit = options.fetch(:limit, -1)
42
+ limit = options.fetch(:limit, -1).try :to_i
39
43
 
40
44
  nodes_ids.each_with_index do |node_id, index|
41
45
  prune, stop = true, options.fetch(:step, 5)
@@ -50,7 +54,13 @@ class Hari::Node::Queries::Relation
50
54
  set = Hari(node_id).sorted_set set_name(options)
51
55
 
52
56
  if from = options[:from].presence
53
- scored_relations_ids = set.range_by_score(from, '+inf', desc: true, with_scores: true, limit: [start, stop])
57
+ args = { desc: true, with_scores: true, limit: [start, stop] }
58
+
59
+ if from[:direction] == 'up'
60
+ scored_relations_ids = set.range_by_score(from[:score], '+inf', args)
61
+ elsif from[:direction] == 'down'
62
+ scored_relations_ids = set.range_by_score('-inf', from[:score], args)
63
+ end
54
64
  else
55
65
  scored_relations_ids = set.range(start, stop, desc: true, with_scores: true)
56
66
  end
@@ -12,6 +12,8 @@ module Hari
12
12
 
13
13
  attr_reader :parent, :direction, :relation, :level, :options
14
14
 
15
+ alias :name :relation
16
+
15
17
  def initialize(parent, direction, relation, *args)
16
18
  @parent, @direction, @relation = parent, direction, relation
17
19
  @level = parent.level + 1
@@ -26,24 +28,22 @@ module Hari
26
28
  end
27
29
 
28
30
  def calculate_limit
29
- options[:limit] ? (options[:limit].to_i - 1) : -1
31
+ options[:limit] || -1
30
32
  end
31
33
 
32
- %w(limit from step).each do |method|
34
+ %w(limit step).each do |method|
33
35
  define_method method do |value|
34
36
  options[method.to_sym] = value
35
37
  self
36
38
  end
37
39
  end
38
40
 
39
- # TODO for later, filter by node type
40
- def types(*types)
41
- options[:types] = types
41
+ def from(score, direction = nil)
42
+ direction ||= :up
43
+ options[:from] = { score: score, direction: direction.to_s }
42
44
  self
43
45
  end
44
46
 
45
- alias :type :types
46
-
47
47
  %w(nodes_ids relations_ids nodes).each do |result_type|
48
48
  define_method result_type do
49
49
  options[:result_type] = result_type.to_sym
@@ -60,6 +60,12 @@ module Hari
60
60
  alias :rel_ids :relations_ids
61
61
  alias :rids :relations_ids
62
62
 
63
+ def type(name)
64
+ fail 'type not supported for chained queries' if level > 1
65
+
66
+ Type.new self, name
67
+ end
68
+
63
69
  def count
64
70
  options[:result_type] = :count
65
71
  result
@@ -0,0 +1,62 @@
1
+ module Hari
2
+ class Node < Entity
3
+ module Queries
4
+ class Type
5
+
6
+ attr_reader :relation, :name
7
+
8
+ def initialize(relation, name)
9
+ @relation, @name = relation, name
10
+ end
11
+
12
+ def intersect_count(type)
13
+ intersect_key = interstore(type)
14
+
15
+ Hari.redis.zcard(intersect_key).tap do
16
+ Hari.redis.del intersect_key
17
+ end
18
+ end
19
+
20
+ def intersect(type, start = nil, stop = nil)
21
+ start ||= 0
22
+ stop ||= -1
23
+
24
+ intersect_key = interstore(type)
25
+
26
+ Hari.redis.zrevrange(intersect_key, start, stop).tap do
27
+ Hari.redis.del intersect_key
28
+ end
29
+ end
30
+
31
+ def sort_by(type, offset = nil, count = nil, order = nil)
32
+ offset ||= 0
33
+ count ||= Hari.redis.zcard(key)
34
+ order = 'desc' unless order.to_s == 'asc'
35
+
36
+ Hari.redis.sort key, by: type.sort_key, order: "#{order} alpha", limit: [offset, count]
37
+ end
38
+
39
+ def interstore(type)
40
+ intersect_key(type).tap do |destination|
41
+ Hari.redis.zinterstore destination, [key, type.key]
42
+ end
43
+ end
44
+
45
+ def key
46
+ start_key = Hari.node_key(relation.parent.node)
47
+ "#{start_key}:#{relation.name}:#{relation.direction}:#{name}"
48
+ end
49
+
50
+ def sort_key
51
+ start_key = Hari.node_key(relation.parent.node)
52
+ "#{start_key}:#{relation.name}:#{name}#*"
53
+ end
54
+
55
+ def intersect_key(type)
56
+ "inter:#{key}:#{type.key}"
57
+ end
58
+
59
+ end
60
+ end
61
+ end
62
+ end
@@ -1,30 +1,32 @@
1
- require 'hari/node/queries/list'
2
- require 'hari/node/queries/set'
3
- require 'hari/node/queries/sorted_set'
4
- require 'hari/node/queries/relation'
5
-
6
1
  module Hari
7
2
  class Node < Entity
8
3
  module Queries
4
+ autoload :Relation, 'hari/node/queries/relation'
5
+ autoload :Type, 'hari/node/queries/type'
9
6
 
10
7
  delegate :in, :out, to: :relation_query
11
8
 
12
9
  delegate :set, :set!, to: :set_query
13
10
  delegate :sorted_set, :sorted_set!, to: :sorted_set_query
14
11
  delegate :list, :list!, to: :list_query
12
+ delegate :string, :string!, to: :string_query
15
13
 
16
14
  private
17
15
 
18
16
  def set_query
19
- Queries::Set.new query_node
17
+ Keys::Set.new query_node
20
18
  end
21
19
 
22
20
  def sorted_set_query
23
- Queries::SortedSet.new query_node
21
+ Keys::SortedSet.new query_node
24
22
  end
25
23
 
26
24
  def list_query
27
- Queries::List.new query_node
25
+ Keys::List.new query_node
26
+ end
27
+
28
+ def string_query
29
+ Keys::String.new query_node
28
30
  end
29
31
 
30
32
  def relation_query
@@ -3,12 +3,41 @@ module Hari
3
3
  module SortedSet
4
4
  extend self
5
5
 
6
- def create(rel)
7
- Relation::DIRECTIONS.each { |d| Hari.redis.zadd rel.key(d), rel.weight(d), rel.id }
6
+ def create(relation)
7
+ Relation::DIRECTIONS.each do |direction|
8
+ key = relation.key(direction)
9
+ weight = relation.weight(direction)
10
+
11
+ Hari.redis.zadd key, weight, relation.id
12
+ Hari.redis.zadd type_key(relation, direction), weight, type_id(relation, direction)
13
+ end
14
+ end
15
+
16
+ def delete(relation)
17
+ Relation::DIRECTIONS.each do |direction|
18
+ key = relation.key(direction)
19
+
20
+ Hari.redis.zrem key, relation.id
21
+ Hari.redis.zrem type_key(relation, direction), type_id(relation, direction)
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def type_key(relation, direction)
28
+ case direction.to_s
29
+ when 'out'
30
+ "#{relation.start_node_id}:#{relation.label}:out:#{Hari.node_type(relation.end_node_id)}"
31
+ when 'in'
32
+ "#{relation.end_node_id}:#{relation.label}:in:#{Hari.node_type(relation.start_node_id)}"
33
+ end
8
34
  end
9
35
 
10
- def delete(rel)
11
- Relation::DIRECTIONS.each { |d| Hari.redis.zrem rel.key(d), rel.id }
36
+ def type_id(relation, direction)
37
+ case direction.to_s
38
+ when 'in' then Hari.node_id(relation.start_node_id)
39
+ when 'out' then Hari.node_id(relation.end_node_id)
40
+ end
12
41
  end
13
42
 
14
43
  end
data/lib/hari/relation.rb CHANGED
@@ -1,4 +1,3 @@
1
- require 'hari/relation/linked_list'
2
1
  require 'hari/relation/sorted_set'
3
2
 
4
3
  module Hari
@@ -54,8 +53,6 @@ module Hari
54
53
  ::Time.now.to_f
55
54
  end
56
55
 
57
- private
58
-
59
56
  def create
60
57
  super
61
58
  backend.create self
data/lib/hari/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Hari
2
- VERSION = '0.0.3'
2
+ VERSION = '0.0.4'
3
3
  end
data/lib/hari.rb CHANGED
@@ -12,12 +12,15 @@ require 'ostruct'
12
12
  require 'hari/version'
13
13
  require 'hari/configuration'
14
14
  require 'hari/errors'
15
- require 'hari/entity'
16
- require 'hari/node'
17
- require 'hari/relation'
18
15
 
19
16
  module Hari
20
17
  extend self
18
+
19
+ autoload :Entity, 'hari/entity'
20
+ autoload :Keys, 'hari/keys'
21
+ autoload :Node, 'hari/node'
22
+ autoload :Relation, 'hari/relation'
23
+
21
24
  extend Configuration
22
25
  extend Hari::Node::Queries
23
26
 
@@ -68,6 +71,10 @@ module Hari
68
71
  Relation.create type, from, target
69
72
  end
70
73
 
74
+ def remove_relation!(type, from, target)
75
+ relation!(type, from, target).delete
76
+ end
77
+
71
78
  end
72
79
 
73
80
  def Hari(arg)
@@ -0,0 +1,41 @@
1
+ require 'spec_helper'
2
+
3
+ describe Hari::Keys::Key do
4
+
5
+ let(:node) { Hari.node user: 10 }
6
+ subject { node.list :friends }
7
+
8
+ specify { subject.instance_of? Hari::Keys::Key }
9
+
10
+ before { subject.add 1 }
11
+
12
+ specify '#exists? + #delete!' do
13
+ subject.exists?.should be_true
14
+
15
+ subject.delete!
16
+
17
+ subject.exists?.should be_false
18
+ end
19
+
20
+ specify '#type' do
21
+ subject.type.should eq('list')
22
+ end
23
+
24
+ specify '#expire + #persist + #ttl' do
25
+ subject.expire 80 # ms
26
+ (subject.ttl.to_i < 80).should be_true
27
+
28
+ sleep 0.1
29
+
30
+ subject.exists?.should be_false
31
+
32
+ subject.add 12
33
+ subject.expire 100
34
+ subject.persist
35
+
36
+ sleep 0.2
37
+
38
+ subject.exists?.should be_true
39
+ end
40
+
41
+ end
@@ -1,6 +1,6 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe Hari::Node::Queries::List do
3
+ describe Hari::Keys::List do
4
4
 
5
5
  let(:node) { Hari.node user: 10 }
6
6
  subject { node.list :friends }
@@ -1,6 +1,6 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe Hari::Node::Queries::Set do
3
+ describe Hari::Keys::Set do
4
4
 
5
5
  let(:node) { Hari.node user: 10 }
6
6
  subject { node.set :friends }
@@ -1,6 +1,6 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe Hari::Node::Queries::SortedSet do
3
+ describe Hari::Keys::SortedSet do
4
4
 
5
5
  let(:node) { Hari.node user: 10 }
6
6
  subject { node.sorted_set :friends }
@@ -0,0 +1,57 @@
1
+ require 'spec_helper'
2
+
3
+ describe Hari::Keys::String do
4
+
5
+ let(:node) { Hari.node user: 10 }
6
+ subject { node.string :bio }
7
+
8
+ specify '#string! + #to_s + #set' do
9
+ subject.set 'lol'
10
+ node.string!(:bio).should eq('lol')
11
+
12
+ subject.to_s.should eq('lol')
13
+ end
14
+
15
+ specify '#length' do
16
+ subject.set 'lol'
17
+ subject.length.should eq(3)
18
+ subject.size.should eq(3)
19
+ end
20
+
21
+ specify '#at + #range + #[]' do
22
+ subject.set 'de his omnibus non cogitavi'
23
+
24
+ subject.at(7).should eq('o')
25
+ subject[7].should eq('o')
26
+
27
+ subject.range(7, 13).should eq('omnibus')
28
+ subject[7, 13].should eq('omnibus')
29
+ subject[7..13].should eq('omnibus')
30
+ end
31
+
32
+ specify '#<<' do
33
+ subject.set 'lol'
34
+ subject << 'omg'
35
+ subject << 'bbq'
36
+
37
+ subject.to_s.should eq('lolomgbbq')
38
+ end
39
+
40
+ specify '#+ + #-' do
41
+ subject.set 1
42
+ subject + 4
43
+ subject.to_s.should eq('5')
44
+
45
+ subject - 7
46
+ subject.to_s.should eq('-2')
47
+ end
48
+
49
+ specify '#bitcount + #getbit + #setbit' do
50
+ subject.set 'lol'
51
+ subject.bitcount.should eq(14)
52
+ subject.getbit(3).should eq(0)
53
+ subject.setbit(4, 1)
54
+ subject.getbit(4).should eq(1)
55
+ end
56
+
57
+ end
@@ -65,6 +65,12 @@ describe Hari::Node do
65
65
  it 'paginates queries' do
66
66
  following = joao.out(:follow).from(15.minutes.ago.to_f).to_a
67
67
  following.map(&:id).sort.should eq [teresa, raimundo].map(&:id).sort
68
+
69
+ following = joao.out(:follow).from(15.minutes.ago.to_f, 'down').to_a
70
+ following.map(&:id).sort.should eq [maria, joaquim, lili].map(&:id).sort
71
+
72
+ following = joao.out(:follow).limit(1).from(15.minutes.ago.to_f, 'down').to_a
73
+ following.map(&:id).sort.should eq [maria.id]
68
74
  end
69
75
 
70
76
  it 'paginates chained queries' do
@@ -73,6 +79,45 @@ describe Hari::Node do
73
79
  following = teresa.out(:follow).out(:follow).from(15.minutes.ago.to_f)
74
80
  following.to_a.map(&:id).sort.should eq [teresa, raimundo].map(&:id).sort
75
81
  following.nodes!.map(&:id).sort.should eq [teresa, raimundo].map(&:id).sort
82
+
83
+ following = teresa.out(:follow).out(:follow).from(15.minutes.ago.to_f, 'down')
84
+ following.to_a.map(&:id).sort.should eq [maria, joaquim, lili].map(&:id).sort
85
+
86
+ following = teresa.out(:follow).out(:follow).limit(1).from(15.minutes.ago.to_f, 'down')
87
+ following.to_a.map(&:id).sort.should eq [maria.id]
88
+ end
89
+ end
90
+
91
+ describe 'queries by type' do
92
+ before do
93
+ Hari.relation! :follow, 'user#1', 'celeb#x'
94
+ Hari.relation! :follow, 'user#2', 'user#1'
95
+ end
96
+
97
+ it 'can intersect type queries' do
98
+ friends = Hari('user#2').out(:follow).type(:user)
99
+ fans = Hari('celeb#x').in(:follow).type(:user)
100
+
101
+ fans.intersect_count(friends).should eq(1)
102
+ friends.intersect_count(fans).should eq(1)
103
+
104
+ fans.intersect(friends).should eq %w(1)
105
+
106
+ Hari.relation! :follow, 'user#3', 'celeb#x'
107
+ Hari.relation! :follow, 'user#4', 'celeb#x'
108
+ Hari.relation! :follow, 'user#5', 'celeb#x'
109
+ Hari.relation! :follow, 'user#6', 'celeb#x'
110
+ Hari.relation! :follow, 'user#7', 'celeb#x'
111
+ Hari.relation! :follow, 'user#8', 'celeb#x'
112
+
113
+ Hari.relation! :follow, 'user#2', 'user#4'
114
+ Hari.relation! :follow, 'user#2', 'user#5'
115
+ Hari.relation! :follow, 'user#2', 'user#6'
116
+ Hari.relation! :follow, 'user#2', 'user#7'
117
+
118
+ fans.intersect(friends, 1, 3).should eq %w(6 5 4)
119
+
120
+ fans.sort_by(friends).take(5).should eq %w(7 6 5 4 1)
76
121
  end
77
122
  end
78
123
 
data/spec/hari_spec.rb CHANGED
@@ -34,4 +34,17 @@ describe Hari do
34
34
  node.node_type.should eq('user')
35
35
  end
36
36
 
37
+ specify '.relation!' do
38
+ Hari.relation! :follow, 'user#1', 'user#2'
39
+
40
+ Hari(user: 1).out(:follow).nodes_ids!.should eq %w(user#2)
41
+ end
42
+
43
+ specify '.remove_relation!' do
44
+ Hari.relation! :follow, 'user#6', 'user#9'
45
+ Hari.remove_relation! :follow, 'user#6', 'user#9'
46
+
47
+ Hari(user: 1).out(:follow).nodes_ids!.should be_empty
48
+ end
49
+
37
50
  end