hari 0.0.4 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/hari.gemspec +10 -2
  3. data/lib/hari.rb +8 -4
  4. data/lib/hari/configuration/redis.rb +6 -2
  5. data/lib/hari/entity.rb +10 -20
  6. data/lib/hari/entity/property.rb +19 -4
  7. data/lib/hari/entity/property/builder.rb +18 -3
  8. data/lib/hari/entity/repository.rb +11 -3
  9. data/lib/hari/entity/serialization.rb +53 -9
  10. data/lib/hari/entity/serialization/array.rb +21 -0
  11. data/lib/hari/entity/serialization/hash.rb +31 -0
  12. data/lib/hari/keys.rb +5 -0
  13. data/lib/hari/keys/hash.rb +67 -0
  14. data/lib/hari/keys/key.rb +24 -3
  15. data/lib/hari/keys/list.rb +10 -8
  16. data/lib/hari/keys/set.rb +8 -8
  17. data/lib/hari/keys/sorted_set.rb +20 -8
  18. data/lib/hari/node.rb +19 -2
  19. data/lib/hari/node/index.rb +152 -0
  20. data/lib/hari/node/queries.rb +32 -17
  21. data/lib/hari/node/queries/relation.rb +14 -1
  22. data/lib/hari/node/queries/relation/backend/sorted_set.rb +41 -90
  23. data/lib/hari/node/queries/relation/backend/sorted_set/count_step.rb +16 -0
  24. data/lib/hari/node/queries/relation/backend/sorted_set/node_step.rb +91 -0
  25. data/lib/hari/node/queries/type.rb +69 -4
  26. data/lib/hari/node/repository.rb +36 -0
  27. data/lib/hari/node/serialization.rb +11 -10
  28. data/lib/hari/object.rb +6 -0
  29. data/lib/hari/serialization.rb +3 -0
  30. data/lib/hari/version.rb +1 -1
  31. data/spec/hari/entity/repository_spec.rb +17 -0
  32. data/spec/hari/entity/serialization/hash_spec.rb +16 -0
  33. data/spec/hari/entity/serialization_spec.rb +14 -4
  34. data/spec/hari/keys/hash_spec.rb +55 -0
  35. data/spec/hari/keys/lists_spec.rb +27 -0
  36. data/spec/hari/node/index_spec.rb +199 -0
  37. data/spec/hari/node_spec.rb +84 -0
  38. data/spec/hari/serialization_spec.rb +41 -0
  39. data/spec/spec_helper.rb +6 -2
  40. metadata +27 -4
@@ -28,7 +28,7 @@ module Hari
28
28
  end
29
29
 
30
30
  def calculate_limit
31
- options[:limit] || -1
31
+ (options[:limit].presence || -1).to_i
32
32
  end
33
33
 
34
34
  %w(limit step).each do |method|
@@ -66,6 +66,10 @@ module Hari
66
66
  Type.new self, name
67
67
  end
68
68
 
69
+ def first
70
+ limit(1).to_a.first
71
+ end
72
+
69
73
  def count
70
74
  options[:result_type] = :count
71
75
  result
@@ -75,6 +79,14 @@ module Hari
75
79
  level == 1 ? parent.node : parent.start_node
76
80
  end
77
81
 
82
+ def <<(nodes)
83
+ fail 'cannot create relation for chained queries' if level > 1
84
+
85
+ Array(nodes).each do |node|
86
+ Hari.relation! relation, parent.node, Hari(node)
87
+ end
88
+ end
89
+
78
90
  def call(final = true)
79
91
  if level == 1
80
92
  backend.fetch parent.node, call_args(final)
@@ -87,6 +99,7 @@ module Hari
87
99
  {
88
100
  relation: relation,
89
101
  direction: direction,
102
+ position: direction == :in ? 0 : 2,
90
103
  limit: calculate_limit,
91
104
  from: options[:from],
92
105
  step: options[:step],
@@ -1,105 +1,56 @@
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
- limit = limit.try(:to_i)
14
-
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]
19
- else
20
- limit -= 1 unless limit <= 0
21
- set.range from, limit, desc: true
1
+ require 'hari/node/queries/relation/backend/sorted_set/node_step'
2
+ require 'hari/node/queries/relation/backend/sorted_set/count_step'
3
+
4
+ module Hari::Node::Queries
5
+ class Relation
6
+ module Backend
7
+ module SortedSet
8
+ extend self
9
+ extend SortedSet::NodeStep
10
+ extend SortedSet::CountStep
11
+
12
+ def fetch(node, options = {})
13
+ set = node.sorted_set set_name(options)
14
+ send "fetch_#{options[:result]}", set, options
22
15
  end
23
- end
24
-
25
- def fetch_nodes_ids(set, options)
26
- index = set.name =~ /in$/ ? 0 : 2
27
- fetch_relations_ids(set, options).map { |r| r.split(':')[index] }
28
- end
29
16
 
30
- def fetch_nodes(set, options)
31
- nodes_ids = fetch_nodes_ids(set, options)
32
- nodes_ids.empty? ? [] : Hari.redis.mget(nodes_ids)
33
- end
34
-
35
- def fetch_count(set, options)
36
- set.count
37
- end
38
-
39
- def step(start_node, nodes_ids, options = {})
40
- stream = start_node.sorted_set("stream:#{SecureRandom.hex(6)}")
41
- direction = options[:direction] == :in ? 0 : 2
42
- limit = options.fetch(:limit, -1).try :to_i
43
-
44
- nodes_ids.each_with_index do |node_id, index|
45
- prune, stop = true, options.fetch(:step, 5)
46
-
47
- if limit == -1 || stream.count < limit
48
- prune, stop = false, limit
17
+ def fetch_relations_ids(set, options)
18
+ from, limit = options.values_at(:from, :limit)
19
+ limit = limit.try(:to_i)
20
+
21
+ if from.present? && from[:direction] == 'up'
22
+ set.range_by_score from[:score], '+inf', desc: true, limit: [0, limit]
23
+ elsif from.present? && from[:direction] == 'down'
24
+ set.range_by_score '-inf', from[:score], desc: true, limit: [0, limit]
25
+ else
26
+ limit -= 1 unless limit <= 0
27
+ set.range from, limit, desc: true
49
28
  end
29
+ end
50
30
 
51
- start, digg = 0, true
52
-
53
- while digg
54
- set = Hari(node_id).sorted_set set_name(options)
55
-
56
- if from = options[:from].presence
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
64
- else
65
- scored_relations_ids = set.range(start, stop, desc: true, with_scores: true)
66
- end
67
-
68
- break if scored_relations_ids.empty?
69
-
70
- scored_nodes_ids = scored_relations_ids.map { |(r, s)| [s, r.split(':')[direction]] }.flatten
71
- stream.add *scored_nodes_ids
72
-
73
- last_node_id = scored_nodes_ids.last
74
-
75
- if prune
76
- stream.trim_by_rank 0, stop
31
+ def fetch_nodes_ids(set, options)
32
+ index = set.name =~ /in$/ ? 0 : 2
33
+ fetch_relations_ids(set, options).map { |r| r.split(':')[index] }
34
+ end
77
35
 
78
- unless stream.include? last_node_id
79
- digg = false
80
- start += stop + 1
81
- end
82
- else
83
- digg = false
84
- end
85
- end
36
+ def fetch_nodes(set, options)
37
+ nodes_ids = fetch_nodes_ids(set, options)
38
+ nodes_ids.empty? ? [] : Hari.redis.mget(nodes_ids)
86
39
  end
87
40
 
88
- nodes_ids = stream.revrange
41
+ def fetch_count(set, options)
42
+ set.count
43
+ end
89
44
 
90
- if nodes_ids.any? && options[:result] == :nodes
91
- Hari.redis.mget nodes_ids
92
- else
93
- nodes_ids
45
+ def step(start_node, nodes_ids, options)
46
+ send "step_#{options[:result]}", start_node, nodes_ids, options
94
47
  end
95
- end
96
48
 
97
- private
49
+ def set_name(options)
50
+ "#{options[:relation]}:#{options[:direction]}"
51
+ end
98
52
 
99
- def set_name(options)
100
- "#{options[:relation]}:#{options[:direction]}"
101
53
  end
102
-
103
54
  end
104
55
  end
105
56
  end
@@ -0,0 +1,16 @@
1
+ module Hari::Node::Queries
2
+ class Relation
3
+ module Backend
4
+ module SortedSet
5
+ module CountStep
6
+
7
+ def step_count(start_node, nodes_ids, options)
8
+ # CONSIDER LIMIT AND FROM
9
+ nodes_ids.inject(0) { |b, n| b + Hari(n).sorted_set(set_name(options)).count }
10
+ end
11
+
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,91 @@
1
+ module Hari::Node::Queries
2
+ class Relation
3
+ module Backend
4
+ module SortedSet
5
+ module NodeStep
6
+
7
+ def step_nodes(start_node, nodes_ids, options)
8
+ stream = start_node.sorted_set("stream:#{SecureRandom.hex(6)}")
9
+
10
+ nodes_ids.each { |node_id| step_node node_id, stream, options }
11
+
12
+ ids = stream.revrange
13
+
14
+ stream.delete!
15
+
16
+ case options[:result]
17
+ when :nodes_ids
18
+ ids
19
+ when :nodes
20
+ ids.any? ? Hari.redis.mget(ids) : []
21
+ end
22
+ end
23
+
24
+ alias :step_nodes_ids :step_nodes
25
+
26
+ def step_node(node_id, stream, options)
27
+ set = Hari(node_id).sorted_set set_name(options)
28
+
29
+ start = 0
30
+ stop = calculate_stop(stream, options)
31
+ prune = stream.count > options[:limit] && options[:limit] != -1
32
+
33
+ digg = true
34
+
35
+ while digg
36
+ if from = options[:from].presence
37
+ args = { desc: true, with_scores: true, limit: [start, stop] }
38
+
39
+ if from[:direction] == 'up'
40
+ scored_relations_ids = set.range_by_score(from[:score], '+inf', args)
41
+ elsif from[:direction] == 'down'
42
+ scored_relations_ids = set.range_by_score('-inf', from[:score], args)
43
+ end
44
+ else
45
+ scored_relations_ids = set.range(start, stop, desc: true, with_scores: true)
46
+ end
47
+
48
+ break if scored_relations_ids.empty?
49
+
50
+ scored_nodes_ids = scored_relations_ids.map { |(r, s)| [s, r.split(':')[options[:position]]] }.flatten
51
+ stream.add *scored_nodes_ids
52
+
53
+ last_node_id = scored_nodes_ids.last
54
+
55
+ if prune
56
+ stream.trim_by_rank 0, stop
57
+
58
+ unless stream.include? last_node_id
59
+ digg = false
60
+ start += stop + 1
61
+ end
62
+ else
63
+ digg = false
64
+ end
65
+ end
66
+
67
+ stream_count = stream.count
68
+
69
+ if options[:limit] != -1 && stream_count > options[:limit]
70
+ trim_stop = stream_count - options[:limit] - 1
71
+ stream.trim_by_rank 0, trim_stop
72
+ end
73
+ end
74
+
75
+ def calculate_stop(stream, options)
76
+ return options[:step].to_i if options[:step].present?
77
+ return options[:limit] if stream.count < options[:limit]
78
+
79
+ case options[:limit]
80
+ when -1, 1...5
81
+ options[:limit]
82
+ else
83
+ 5
84
+ end
85
+ end
86
+
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
@@ -3,10 +3,11 @@ module Hari
3
3
  module Queries
4
4
  class Type
5
5
 
6
- attr_reader :relation, :name
6
+ attr_reader :relation, :name, :options
7
7
 
8
8
  def initialize(relation, name)
9
9
  @relation, @name = relation, name
10
+ @options = {}
10
11
  end
11
12
 
12
13
  def intersect_count(type)
@@ -42,20 +43,84 @@ module Hari
42
43
  end
43
44
  end
44
45
 
46
+ def count
47
+ Hari.redis.zcard key
48
+ end
49
+
50
+ def ids
51
+ start = options[:start] || 0
52
+ stop = options[:stop] || -1
53
+
54
+ Hari.redis.zrevrange key, start, stop
55
+ end
56
+
57
+ def nodes_ids
58
+ ids.map { |id| "#{name}##{id}" }
59
+ end
60
+
61
+ alias :nodes_ids! :nodes_ids
62
+ alias :n_ids :nodes_ids
63
+ alias :nids :nodes_ids
64
+
65
+ def nodes
66
+ if ids = nodes_ids.presence
67
+ Hari.redis.mget(ids).map &Hari::Node.method(:from_source)
68
+ end
69
+ end
70
+
71
+ alias :nodes! :nodes
72
+ alias :to_a :nodes
73
+
74
+ def relations_ids
75
+ ids.map &method(:relation_key)
76
+ end
77
+
78
+ alias :relations_ids! :relations_ids
79
+ alias :rids :relations_ids
80
+ alias :rel_ids :relations_ids
81
+
82
+ def limit(start, stop)
83
+ options.merge! start: start, stop: stop
84
+
85
+ self
86
+ end
87
+
88
+ def <<(ids)
89
+ Array(ids).each do |id|
90
+ Hari.relation! relation.name, relation.parent.node, Hari(name => id)
91
+ end
92
+ end
93
+
94
+ def where(conditions = {})
95
+ type_class.where(conditions).tap do |index|
96
+ index.indexes << self
97
+ end
98
+ end
99
+
45
100
  def key
46
- start_key = Hari.node_key(relation.parent.node)
47
101
  "#{start_key}:#{relation.name}:#{relation.direction}:#{name}"
48
102
  end
49
103
 
50
104
  def sort_key
51
- start_key = Hari.node_key(relation.parent.node)
52
- "#{start_key}:#{relation.name}:#{name}#*"
105
+ relation_key '*'
106
+ end
107
+
108
+ def relation_key(id)
109
+ "#{start_key}:#{relation.name}:#{name}##{id}"
53
110
  end
54
111
 
55
112
  def intersect_key(type)
56
113
  "inter:#{key}:#{type.key}"
57
114
  end
58
115
 
116
+ def start_key
117
+ Hari.node_key relation.parent.node
118
+ end
119
+
120
+ def type_class
121
+ name.to_s.camelize.constantize
122
+ end
123
+
59
124
  end
60
125
  end
61
126
  end
@@ -3,6 +3,27 @@ module Hari
3
3
  module Repository
4
4
  extend ActiveSupport::Concern
5
5
 
6
+ def reindex(options = {})
7
+ self.class.indexed_properties.each do |property|
8
+ if change = previous_changes[property.name]
9
+ previous, current = change
10
+
11
+ Index.new(property, previous).delete self
12
+ Index.new(property, current).add self
13
+ elsif options[:force_index]
14
+ value = send(property.name)
15
+ Index.new(property, value).add self
16
+ end
17
+ end
18
+ end
19
+
20
+ def remove_from_indexes
21
+ self.class.indexed_properties.each do |property|
22
+ value = send(property.name)
23
+ Index.new(property, value).delete self
24
+ end
25
+ end
26
+
6
27
  module ClassMethods
7
28
 
8
29
  def find_one(id, options = {})
@@ -18,6 +39,21 @@ module Hari
18
39
  super ids, options
19
40
  end
20
41
 
42
+ def find_by(name, value)
43
+ if property = indexed_properties.find { |p| p.name.to_s == name.to_s }
44
+ Index.new property, value
45
+ else
46
+ fail "missing index for key #{name}"
47
+ end
48
+ end
49
+
50
+ def where(conditions = {})
51
+ conditions.inject(nil) do |index, (key, value)|
52
+ query = find_by(key, value)
53
+ index ? index.append(query) : query
54
+ end
55
+ end
56
+
21
57
  end
22
58
  end
23
59
  end
@@ -5,18 +5,19 @@ module Hari
5
5
  def from_source(source)
6
6
  return if source.blank?
7
7
 
8
- hash = Yajl::Parser.parse(source)
9
- klass = hash['id'].split('#').first.camelize.constantize
10
-
11
- attrs = hash.inject({}) do |buffer, (key, value)|
12
- if prop = klass.properties.find { |p| p.name == key }
13
- buffer[key] = prop.desserialize(value)
14
- end
15
-
16
- buffer
8
+ case source
9
+ when ::String
10
+ hash = Yajl::Parser.parse(source)
11
+ source_class(hash).from_hash hash
12
+ when ::Hash
13
+ source_class(source).from_hash source
17
14
  end
15
+ end
16
+
17
+ private
18
18
 
19
- klass.new attrs
19
+ def source_class(source)
20
+ source['id'].split('#').first.camelize.constantize
20
21
  end
21
22
 
22
23
  end