hari 0.0.3 → 0.0.4

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,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