hari 0.0.1 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +15 -0
  2. data/.travis.yml +15 -0
  3. data/LICENSE +15 -19
  4. data/README.md +22 -0
  5. data/hari.gemspec +2 -1
  6. data/lib/hari.rb +56 -1
  7. data/lib/hari/entity.rb +8 -11
  8. data/lib/hari/entity/property.rb +2 -2
  9. data/lib/hari/entity/property/builder.rb +9 -1
  10. data/lib/hari/entity/repository.rb +3 -2
  11. data/lib/hari/entity/serialization/float.rb +2 -0
  12. data/lib/hari/entity/serialization/integer.rb +3 -10
  13. data/lib/hari/node.rb +20 -3
  14. data/lib/hari/node/queries.rb +33 -0
  15. data/lib/hari/node/queries/list.rb +147 -0
  16. data/lib/hari/node/queries/relation.rb +100 -0
  17. data/lib/hari/node/queries/relation/backend/list.rb +35 -0
  18. data/lib/hari/node/queries/relation/backend/sorted_set.rb +95 -0
  19. data/lib/hari/node/queries/relation/runnable.rb +24 -0
  20. data/lib/hari/node/queries/relation/start.rb +19 -0
  21. data/lib/hari/node/queries/relation/step.rb +17 -0
  22. data/lib/hari/node/queries/set.rb +116 -0
  23. data/lib/hari/node/queries/sorted_set.rb +130 -0
  24. data/lib/hari/node/repository.rb +24 -0
  25. data/lib/hari/node/serialization.rb +24 -0
  26. data/lib/hari/relation.rb +72 -0
  27. data/lib/hari/relation/linked_list.rb +16 -0
  28. data/lib/hari/relation/sorted_set.rb +16 -0
  29. data/lib/hari/version.rb +1 -1
  30. data/spec/hari/configuration_spec.rb +5 -0
  31. data/spec/hari/entity/repository_spec.rb +4 -4
  32. data/spec/hari/entity/serialization_spec.rb +2 -2
  33. data/spec/hari/node/lists_spec.rb +126 -0
  34. data/spec/hari/node/sets_spec.rb +85 -0
  35. data/spec/hari/node/sorted_sets_spec.rb +89 -0
  36. data/spec/hari/node_spec.rb +79 -0
  37. data/spec/hari_spec.rb +37 -0
  38. data/spec/spec_helper.rb +11 -1
  39. metadata +48 -32
  40. data/lib/hari/relationship.rb +0 -72
  41. data/lib/hari/relationship/linked_list.rb +0 -22
  42. data/lib/hari/relationship/sorted_set.rb +0 -22
  43. data/lib/hari/scripts.rb +0 -26
@@ -0,0 +1,100 @@
1
+ require 'hari/node/queries/relation/step'
2
+ require 'hari/node/queries/relation/start'
3
+ require 'hari/node/queries/relation/runnable'
4
+ require 'hari/node/queries/relation/backend/sorted_set'
5
+
6
+ module Hari
7
+ class Node < Entity
8
+ module Queries
9
+ class Relation
10
+ include Relation::Step
11
+ include Relation::Runnable
12
+
13
+ attr_reader :parent, :direction, :relation, :level, :options
14
+
15
+ def initialize(parent, direction, relation, *args)
16
+ @parent, @direction, @relation = parent, direction, relation
17
+ @level = parent.level + 1
18
+ @options = {}
19
+ args.extract_options!.each { |k, v| send k, v }
20
+
21
+ @options[:backend] = args.first.presence || Backend::SortedSet
22
+ end
23
+
24
+ def backend
25
+ options[:backend]
26
+ end
27
+
28
+ def calculate_limit
29
+ options[:limit] ? (options[:limit].to_i - 1) : -1
30
+ end
31
+
32
+ %w(limit from step).each do |method|
33
+ define_method method do |value|
34
+ options[method.to_sym] = value
35
+ self
36
+ end
37
+ end
38
+
39
+ # TODO for later, filter by node type
40
+ def types(*types)
41
+ options[:types] = types
42
+ self
43
+ end
44
+
45
+ alias :type :types
46
+
47
+ %w(nodes_ids relations_ids nodes).each do |result_type|
48
+ define_method result_type do
49
+ options[:result_type] = result_type.to_sym
50
+ self
51
+ end
52
+
53
+ define_method "#{result_type}!" do
54
+ send result_type
55
+ result
56
+ end
57
+ end
58
+
59
+ alias :nids :nodes_ids
60
+ alias :rel_ids :relations_ids
61
+ alias :rids :relations_ids
62
+
63
+ def count
64
+ options[:result_type] = :count
65
+ result
66
+ end
67
+
68
+ def start_node
69
+ level == 1 ? parent.node : parent.start_node
70
+ end
71
+
72
+ def call(final = true)
73
+ if level == 1
74
+ backend.fetch parent.node, call_args(final)
75
+ else
76
+ backend.step start_node, parent.call(false), call_args(final)
77
+ end
78
+ end
79
+
80
+ def call_args(final = true)
81
+ {
82
+ relation: relation,
83
+ direction: direction,
84
+ limit: calculate_limit,
85
+ from: options[:from],
86
+ step: options[:step],
87
+ result: result_type(final)
88
+ }
89
+ end
90
+
91
+ def result_type(final = false)
92
+ return :nodes_ids unless final
93
+
94
+ options.fetch :result_type, :nodes
95
+ end
96
+
97
+ end
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,35 @@
1
+ module Hari::Node::Queries::Relation
2
+ module Backend
3
+ module List
4
+ extend self
5
+
6
+ def fetch(node, options = {})
7
+ list = node.list list_name(options)
8
+ send "fetch_#{options[:result]}", list, options
9
+ end
10
+
11
+ def fetch_relations_ids(list, options = {})
12
+ start = options.fetch(:from, 0)
13
+ stop = options.limit(:limit, -1)
14
+
15
+ list.range start, stop
16
+ end
17
+
18
+ def fetch_nodes_ids(list, options)
19
+ index = list.name =~ /in$/ ? 1 : 2
20
+ fetch_relations_ids(list, options).map { |r| r.split(':')[index] }
21
+ end
22
+
23
+ def fetch_count(list, options)
24
+ list.count
25
+ end
26
+
27
+ private
28
+
29
+ def list_name(options)
30
+ "#{options[:relation]}:#{options[:direction]}"
31
+ end
32
+
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,95 @@
1
+ class Hari::Node::Queries::Relation
2
+ module Backend
3
+ module SortedSet
4
+ extend self
5
+
6
+ def fetch(node, options = {})
7
+ set = node.sorted_set set_name(options)
8
+ send "fetch_#{options[:result]}", set, options
9
+ end
10
+
11
+ def fetch_relations_ids(set, options)
12
+ from, limit = options.values_at(:from, :limit)
13
+
14
+ if from.present?
15
+ set.range_by_score from, '+inf', desc: true, limit: [0, limit]
16
+ else
17
+ set.range from, limit, desc: true
18
+ end
19
+ end
20
+
21
+ def fetch_nodes_ids(set, options)
22
+ index = set.name =~ /in$/ ? 0 : 2
23
+ fetch_relations_ids(set, options).map { |r| r.split(':')[index] }
24
+ end
25
+
26
+ def fetch_nodes(set, options)
27
+ nodes_ids = fetch_nodes_ids(set, options)
28
+ nodes_ids.empty? ? [] : Hari.redis.mget(nodes_ids)
29
+ end
30
+
31
+ def fetch_count(set, options)
32
+ set.count
33
+ end
34
+
35
+ def step(start_node, nodes_ids, options = {})
36
+ stream = start_node.sorted_set("stream:#{SecureRandom.hex(6)}")
37
+ direction = options[:direction] == :in ? 0 : 2
38
+ limit = options.fetch(:limit, -1)
39
+
40
+ nodes_ids.each_with_index do |node_id, index|
41
+ prune, stop = true, options.fetch(:step, 5)
42
+
43
+ if limit == -1 || stream.count < limit
44
+ prune, stop = false, limit
45
+ end
46
+
47
+ start, digg = 0, true
48
+
49
+ while digg
50
+ set = Hari(node_id).sorted_set set_name(options)
51
+
52
+ if from = options[:from].presence
53
+ scored_relations_ids = set.range_by_score(from, '+inf', desc: true, with_scores: true, limit: [start, stop])
54
+ else
55
+ scored_relations_ids = set.range(start, stop, desc: true, with_scores: true)
56
+ end
57
+
58
+ break if scored_relations_ids.empty?
59
+
60
+ scored_nodes_ids = scored_relations_ids.map { |(r, s)| [s, r.split(':')[direction]] }.flatten
61
+ stream.add *scored_nodes_ids
62
+
63
+ last_node_id = scored_nodes_ids.last
64
+
65
+ if prune
66
+ stream.trim_by_rank 0, stop
67
+
68
+ unless stream.include? last_node_id
69
+ digg = false
70
+ start += stop + 1
71
+ end
72
+ else
73
+ digg = false
74
+ end
75
+ end
76
+ end
77
+
78
+ nodes_ids = stream.revrange
79
+
80
+ if nodes_ids.any? && options[:result] == :nodes
81
+ Hari.redis.mget nodes_ids
82
+ else
83
+ nodes_ids
84
+ end
85
+ end
86
+
87
+ private
88
+
89
+ def set_name(options)
90
+ "#{options[:relation]}:#{options[:direction]}"
91
+ end
92
+
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,24 @@
1
+ module Hari
2
+ class Node < Entity
3
+ module Queries
4
+ class Relation
5
+ module Runnable
6
+
7
+ def result
8
+ result = call(true)
9
+
10
+ case result_type(true)
11
+ when :nodes
12
+ result.map &Hari::Node.method(:from_source)
13
+ else
14
+ result
15
+ end
16
+ end
17
+
18
+ alias :to_a :result
19
+
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,19 @@
1
+ module Hari
2
+ class Node < Entity
3
+ module Queries
4
+ class Relation
5
+ class Start
6
+ include Relation::Step
7
+
8
+ attr_reader :node, :level
9
+
10
+ def initialize(node)
11
+ @node = node
12
+ @level = 0
13
+ end
14
+
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,17 @@
1
+ module Hari
2
+ class Node < Entity
3
+ module Queries
4
+ class Relation
5
+ module Step
6
+
7
+ %w(in out).each do |direction|
8
+ define_method direction do |*args|
9
+ Relation.new self, direction, *args
10
+ end
11
+ end
12
+
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,116 @@
1
+ module Hari
2
+ class Node < Entity
3
+ module Queries
4
+ class Set
5
+
6
+ attr_reader :node, :name
7
+
8
+ def initialize(node = nil)
9
+ @node = node
10
+ end
11
+
12
+ def key
13
+ @key ||= begin
14
+ prefix = node ? "#{Hari.node_key(node)}:" : ''
15
+ prefix + name.to_s
16
+ end
17
+ end
18
+
19
+ def set(name)
20
+ @name = name
21
+ self
22
+ end
23
+
24
+ def set!(name)
25
+ @name = name
26
+ members
27
+ end
28
+
29
+ def members
30
+ Hari.redis.smembers key
31
+ end
32
+
33
+ def rand(count = 1)
34
+ Hari.redis.srandmember key, count
35
+ end
36
+
37
+ def count
38
+ Hari.redis.scard key
39
+ end
40
+
41
+ alias :size :count
42
+ alias :length :count
43
+
44
+ def empty?
45
+ count == 0
46
+ end
47
+
48
+ def one?
49
+ count == 1
50
+ end
51
+
52
+ def many?
53
+ count > 1
54
+ end
55
+
56
+ def include?(member)
57
+ Hari.redis.sismember key, member
58
+ end
59
+
60
+ alias :member? :include?
61
+
62
+ def add(*members)
63
+ Hari.redis.sadd key, members
64
+ end
65
+
66
+ def <<(member)
67
+ add member
68
+ end
69
+
70
+ def delete(*members)
71
+ Hari.redis.srem key, members
72
+ end
73
+
74
+ def pop
75
+ Hari.redis.spop key
76
+ end
77
+
78
+ def intersect(*set_queries)
79
+ Hari.redis.sinter key, set_query_keys(set_queries)
80
+ end
81
+
82
+ def &(other_set_query)
83
+ intersect other_set_query
84
+ end
85
+
86
+ def diff(*set_queries)
87
+ Hari.redis.sdiff key, set_query_keys(set_queries)
88
+ end
89
+
90
+ def -(other_set_query)
91
+ diff other_set_query
92
+ end
93
+
94
+ private
95
+
96
+ def set_query_keys(set_queries)
97
+ keys = set_queries.map do |query|
98
+ ensure_set_query! query
99
+ query.key
100
+ end
101
+
102
+ fail 'no query keys' if keys.empty?
103
+
104
+ keys
105
+ end
106
+
107
+ def ensure_set_query!(query)
108
+ unless query.kind_of?(Hari::Node::Queries::Set)
109
+ fail 'not a set query'
110
+ end
111
+ end
112
+
113
+ end
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,130 @@
1
+ module Hari
2
+ class Node < Entity
3
+ module Queries
4
+ class SortedSet
5
+
6
+ attr_reader :node, :name
7
+
8
+ def initialize(node = nil)
9
+ @node = node
10
+ end
11
+
12
+ def key
13
+ @key ||= begin
14
+ prefix = node ? "#{Hari.node_key(node)}:" : ''
15
+ prefix + name.to_s
16
+ end
17
+ end
18
+
19
+ def sorted_set(name)
20
+ @name = name
21
+ self
22
+ end
23
+
24
+ def sorted_set!(name)
25
+ @name = name
26
+ members
27
+ end
28
+
29
+ def range(start = 0, stop = -1, options = {})
30
+ return revrange(start, stop, options) if options[:desc]
31
+
32
+ Hari.redis.zrange key, start, stop, options.slice(:with_scores)
33
+ end
34
+
35
+ alias :members :range
36
+
37
+ def range_with_scores
38
+ range 0, -1, with_scores: true
39
+ end
40
+
41
+ def revrange(start = 0, stop = -1, options = {})
42
+ Hari.redis.zrevrange key, start, stop, options.slice(:with_scores)
43
+ end
44
+
45
+ alias :reverse_range :revrange
46
+ alias :desc_range :revrange
47
+
48
+ def revrange_with_scores
49
+ revrange 0, -1, with_scores: true
50
+ end
51
+
52
+ def range_by_score(min, max, options = {})
53
+ return revrange_by_score(min, max, options) if options[:desc]
54
+
55
+ Hari.redis.zrangebyscore key, min, max, options.slice(:with_scores, :limit)
56
+ end
57
+
58
+ def revrange_by_score(min, max, options = {})
59
+ Hari.redis.zrevrangebyscore key, max, min, options.slice(:with_scores, :limit)
60
+ end
61
+
62
+ def rank(member, options = {})
63
+ return revrank(member, options) if options[:desc]
64
+
65
+ Hari.redis.zrank key, member
66
+ end
67
+
68
+ alias :ranking :rank
69
+ alias :position :rank
70
+
71
+ def revrank(member)
72
+ Hari.redis.zrevrank key, member
73
+ end
74
+
75
+ alias :reverse_ranking :revrank
76
+ alias :reverse_position :revrank
77
+
78
+ def count
79
+ Hari.redis.zcard key
80
+ end
81
+
82
+ alias :size :count
83
+ alias :length :count
84
+
85
+ def empty?
86
+ count == 0
87
+ end
88
+
89
+ def one?
90
+ count == 1
91
+ end
92
+
93
+ def many?
94
+ count > 1
95
+ end
96
+
97
+ def include?(member)
98
+ score(member).present?
99
+ end
100
+
101
+ alias :member? :include?
102
+
103
+ def score(member)
104
+ Hari.redis.zscore key, member
105
+ end
106
+
107
+ def add(*score_members)
108
+ Hari.redis.zadd key, score_members.to_a.flatten
109
+ end
110
+
111
+ def <<(*score_members)
112
+ add score_members
113
+ end
114
+
115
+ def delete(*members)
116
+ Hari.redis.zrem key, members
117
+ end
118
+
119
+ def trim_by_rank(start, stop)
120
+ Hari.redis.zremrangebyrank key, start, stop
121
+ end
122
+
123
+ def trim_by_score(min, max)
124
+ Hari.redis.zremrangebyscore key, min, max
125
+ end
126
+
127
+ end
128
+ end
129
+ end
130
+ end