hari 0.0.4 → 0.0.5

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.
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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6bec03d308713945bce8a8b110c663beb57450f7
4
- data.tar.gz: 4679c39080577b390539c16921264c6d69ce0b50
3
+ metadata.gz: ca49682c61cc3b4b1a347e83c9d45e9a06747cb9
4
+ data.tar.gz: 3c89e8825f0286262ac6a7a4a3f0acbaa0a46191
5
5
  SHA512:
6
- metadata.gz: 8d1b9c6f45f02be08c99b3f01286031d7c94a64a91d9f14c904acfd9934e0c3c9fa986cdf05a77b8af93d4e37ba25d2d8837d59fc1082cf112c315229a306ab3
7
- data.tar.gz: 984b40a10bc769cb874b6879bfd10de44b970225480ed2d73285d6536ef5aed4f148d114721e160c7a79fc6ecd73646fc7e9f42edfe72c47a740579d35c9f139
6
+ metadata.gz: d5c0e5c57e863e065d0fe700d30a143cbab436f44b822ac8cfc26fcc3da0e3547517db2a565d6a402fa737d025851e18e9663f311d5c4d643cced4796d86a71f
7
+ data.tar.gz: 4d354e66a0ef1b90abf287d2a59bbc836fe25368a5ab590ca2f56b0a14f84d69b6f63fe76a5c01da51e6e0a232179078553404f7c385f9926c1a141828ab09e0
@@ -8,9 +8,17 @@ require 'hari/version'
8
8
  Gem::Specification.new do |s|
9
9
  s.name = 'hari'
10
10
  s.version = Hari::VERSION
11
- s.summary = 'Hari is a graph library on top of Redis + Lua scripts'
11
+ s.summary = 'A tool to abstract complex relationships between ' +
12
+ 'Ruby objects onto Redis data structures.'
13
+
12
14
  s.description = <<-MD
13
- Hari is a graph library on top of Redis database + Lua scripts
15
+ Hari is a tool to abstract complex relationships between
16
+ Ruby objects onto Redis data structures.
17
+
18
+ It allows for expressive querying of those relationships
19
+ as well, in an easy way. It is mostly geared towards
20
+ typical social networking concepts like news feeds,
21
+ activity logs, friends of friends, mutual friends, and so on.
14
22
  MD
15
23
 
16
24
  s.author = 'Victor Rodrigues'
@@ -1,5 +1,6 @@
1
1
  require 'redis'
2
2
  require 'redis/namespace'
3
+ require 'redis/connection/hiredis'
3
4
  require 'active_model'
4
5
  require 'active_support/core_ext/hash/indifferent_access'
5
6
  require 'active_support/core_ext/module/delegation'
@@ -8,6 +9,7 @@ require 'active_support/core_ext/string/inflections'
8
9
  require 'yajl'
9
10
  require 'erb'
10
11
  require 'ostruct'
12
+ require 'digest/md5'
11
13
 
12
14
  require 'hari/version'
13
15
  require 'hari/configuration'
@@ -16,10 +18,12 @@ require 'hari/errors'
16
18
  module Hari
17
19
  extend self
18
20
 
19
- autoload :Entity, 'hari/entity'
20
- autoload :Keys, 'hari/keys'
21
- autoload :Node, 'hari/node'
22
- autoload :Relation, 'hari/relation'
21
+ autoload :Entity, 'hari/entity'
22
+ autoload :Keys, 'hari/keys'
23
+ autoload :Node, 'hari/node'
24
+ autoload :Object, 'hari/object'
25
+ autoload :Relation, 'hari/relation'
26
+ autoload :Serialization, 'hari/serialization'
23
27
 
24
28
  extend Configuration
25
29
  extend Hari::Node::Queries
@@ -16,9 +16,13 @@ module Hari
16
16
  private
17
17
 
18
18
  def redis_namespace(server)
19
- return server if server.kind_of?(::Redis::Namespace)
19
+ prefix = 'hari'
20
20
 
21
- ::Redis::Namespace.new :hari, redis_server(server)
21
+ if server.kind_of?(::Redis::Namespace)
22
+ prefix = "#{server.namespace}:#{prefix}"
23
+ end
24
+
25
+ ::Redis::Namespace.new prefix, redis_server(server)
22
26
  end
23
27
 
24
28
  def redis_server(server)
@@ -1,18 +1,12 @@
1
- require 'hari/entity/property'
2
- require 'hari/entity/repository'
3
- require 'hari/entity/serialization'
4
-
5
1
  module Hari
6
2
  class Entity
7
3
  extend ActiveModel::Naming
8
4
  extend ActiveModel::Callbacks
9
- include ActiveModel::Validations
10
5
 
11
6
  autoload :Property, 'hari/entity/property'
12
- autoload :Repository, 'hari/entity/property'
13
- autoload :Serialization, 'hari/entity/property'
7
+ autoload :Repository, 'hari/entity/repository'
8
+ autoload :Serialization, 'hari/entity/serialization'
14
9
 
15
- extend Property::Builder
16
10
  include Repository
17
11
  include Serialization
18
12
 
@@ -23,27 +17,23 @@ module Hari
23
17
  property :updated_at, type: Time
24
18
 
25
19
  def initialize(attrs = {})
20
+ update_attributes attrs, save: false
21
+ end
22
+
23
+ def update_attributes(attrs = {}, options = {})
26
24
  return if attrs.blank?
27
25
 
28
26
  attrs = attrs.with_indifferent_access
29
27
 
30
28
  self.class.properties.each do |prop|
31
- send("#{prop.name}=", attrs[prop.name]) if attrs[prop.name]
29
+ write_attribute prop.name, attrs[prop.name] if attrs.key?(prop.name)
32
30
  end
33
- end
34
31
 
35
- def attributes
36
- self.class.properties.inject({}) do |buffer, prop|
37
- buffer.merge prop.name => send(prop.name)
38
- end
32
+ save if options.fetch(:save, true)
39
33
  end
40
34
 
41
- alias :attribute :send
42
- alias :read_attribute :send
43
- alias :has_attribute? :respond_to?
44
-
45
- def write_attribute(name, value)
46
- send "#{name}=", value
35
+ def update_attribute(attribute, value)
36
+ update_attributes attribute => value
47
37
  end
48
38
 
49
39
  def ==(other)
@@ -3,14 +3,29 @@ module Hari
3
3
  class Property
4
4
  autoload :Builder, 'hari/entity/property/builder'
5
5
 
6
- attr_accessor :name, :serializer, :options
6
+ attr_accessor :entity, :name, :serializer, :options
7
7
 
8
- def initialize(name, options = {})
9
- @name, @options = name.to_s, options
8
+ def initialize(entity, name, options = {})
9
+ @entity, @name, @options = entity, name.to_s, options
10
10
  @serializer = options.delete(:type) || Serialization::String
11
11
  end
12
12
 
13
- def serialize(value)
13
+ def default
14
+ case options[:default]
15
+ when Proc
16
+ options[:default].call
17
+ else
18
+ options[:default]
19
+ end
20
+ end
21
+
22
+ def serialize(entity)
23
+ value = entity.attribute(name)
24
+
25
+ if value.nil?
26
+ value = entity.write_attribute(name, default)
27
+ end
28
+
14
29
  serializer.serialize value, name: name
15
30
  end
16
31
 
@@ -4,10 +4,24 @@ module Hari
4
4
  module Builder
5
5
 
6
6
  def property(name, options = {})
7
- attr_accessor name
7
+ attr_reader name
8
+ define_attribute_method name
9
+
8
10
  validates_presence_of name if options[:required]
9
11
 
10
- self.properties << Property.new(name, options)
12
+ define_method "#{name}=" do |value|
13
+ unless send(name) == value
14
+ send "#{name}_will_change!"
15
+ end
16
+
17
+ instance_variable_set "@#{name}", value
18
+ end
19
+
20
+ if options[:type] == Serialization::Boolean
21
+ define_method("#{name}?") { send name }
22
+ end
23
+
24
+ properties << Property.new(self, name, options)
11
25
  end
12
26
 
13
27
  def properties(*args)
@@ -22,7 +36,8 @@ module Hari
22
36
  a.ancestors.include? Hari::Entity
23
37
  end
24
38
 
25
- entities_ancestors[1].properties.dup # the closest
39
+ entity = entities_ancestors[1]
40
+ entity ? entity.properties.dup : []
26
41
  end
27
42
  end
28
43
  end
@@ -4,7 +4,9 @@ module Hari
4
4
  extend ActiveSupport::Concern
5
5
 
6
6
  def create_or_update
7
- run_callbacks(:save) { new? ? create : update }
7
+ run_callbacks(:save) { new? ? create : update }.tap do
8
+ @changed_attributes.clear
9
+ end
8
10
  end
9
11
 
10
12
  alias :save :create_or_update
@@ -14,7 +16,7 @@ module Hari
14
16
  fail Hari::ValidationsFailed, self unless valid?
15
17
 
16
18
  @id ||= generate_id
17
- self.created_at = Time.now
19
+ @created_at ||= Time.now
18
20
  self.updated_at = Time.now
19
21
  persist
20
22
  end
@@ -34,7 +36,9 @@ module Hari
34
36
  end
35
37
 
36
38
  def persist
37
- Hari.redis.set id, to_json
39
+ source = to_json
40
+ @previously_changed = changes
41
+ Hari.redis.set id, source
38
42
  end
39
43
 
40
44
  def delete
@@ -57,6 +61,8 @@ module Hari
57
61
  def find(*args)
58
62
  options = args.extract_options!
59
63
  args.flatten!
64
+ return if args.empty?
65
+
60
66
  args = args.map { |a| a.to_s.gsub(/^hari\:/, '') }
61
67
  args.one? ? find_one(args[0], options) : find_many(args, options)
62
68
  end
@@ -66,6 +72,8 @@ module Hari
66
72
  end
67
73
 
68
74
  def find_many(ids, options = {})
75
+ return [] if ids.empty?
76
+
69
77
  Hari.redis.mget(ids).map &method(:from_json)
70
78
  end
71
79
 
@@ -3,28 +3,61 @@ module Hari
3
3
  module Serialization
4
4
  extend ActiveSupport::Concern
5
5
 
6
+ included do
7
+ include ActiveModel::Validations
8
+ include ActiveModel::Dirty
9
+ extend Property::Builder
10
+ end
11
+
12
+ autoload :Array, 'hari/entity/serialization/array'
6
13
  autoload :Boolean, 'hari/entity/serialization/boolean'
7
14
  autoload :Date, 'hari/entity/serialization/date'
8
15
  autoload :DateTime, 'hari/entity/serialization/datetime'
9
16
  autoload :Float, 'hari/entity/serialization/float'
17
+ autoload :Hash, 'hari/entity/serialization/hash'
10
18
  autoload :Integer, 'hari/entity/serialization/integer'
11
19
  autoload :String, 'hari/entity/serialization/string'
12
20
  autoload :Time, 'hari/entity/serialization/time'
13
21
 
14
- def to_json
15
- hash = self.class.properties.inject({}) do |buffer, prop|
16
- buffer.merge prop.name => prop.serialize(send(prop.name))
22
+ def initialize(attrs = {})
23
+ return if attrs.blank?
24
+
25
+ attrs = attrs.with_indifferent_access
26
+
27
+ self.class.properties.each do |prop|
28
+ write_attribute(prop.name, attrs[prop.name]) if attrs.key?(prop.name)
17
29
  end
30
+ end
18
31
 
19
- Yajl::Encoder.encode hash
32
+ def attributes
33
+ self.class.properties.inject({}) do |buffer, prop|
34
+ buffer.merge prop.name => send(prop.name)
35
+ end
20
36
  end
21
37
 
22
- module ClassMethods
38
+ alias :attribute :send
39
+ alias :read_attribute :send
40
+ alias :has_attribute? :respond_to?
41
+ alias :read_attribute_for_serialization :send
23
42
 
24
- def from_json(source)
25
- return if source.blank?
43
+ def write_attribute(name, value)
44
+ send "#{name}=", value
45
+ end
26
46
 
27
- attrs = Yajl::Parser.parse(source).inject({}) do |buffer, (key, value)|
47
+ def to_hash
48
+ self.class.properties.inject({}) do |buffer, prop|
49
+ buffer.merge prop.name => prop.serialize(self)
50
+ end
51
+ end
52
+
53
+ def to_json
54
+ Yajl::Encoder.encode to_hash
55
+ end
56
+
57
+ module ClassMethods
58
+
59
+ def from_hash(source)
60
+ hash = source.inject({}) do |buffer, (key, value)|
28
61
  if prop = properties.find { |p| p.name == key }
29
62
  buffer[key] = prop.desserialize(value)
30
63
  end
@@ -32,7 +65,18 @@ module Hari
32
65
  buffer
33
66
  end
34
67
 
35
- new attrs
68
+ new(hash).tap { |e| e.changed_attributes.clear }
69
+ end
70
+
71
+ def from_json(source)
72
+ return if source.blank?
73
+
74
+ case source
75
+ when ::String
76
+ from_hash Yajl::Parser.parse(source)
77
+ when ::Hash
78
+ from_hash source
79
+ end
36
80
  end
37
81
 
38
82
  end
@@ -0,0 +1,21 @@
1
+ module Hari
2
+ class Entity
3
+ module Serialization
4
+ module Array
5
+
6
+ def self.serialize(value, options = {})
7
+ Array value
8
+ end
9
+
10
+ def self.desserialize(value, options = {})
11
+ Array value
12
+ end
13
+
14
+ def self.method_missing(method, *args, &block)
15
+ ::Array.send method, *args, &block
16
+ end
17
+
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,31 @@
1
+ module Hari
2
+ class Entity
3
+ module Serialization
4
+ module Hash
5
+
6
+ def self.serialize(value, options = {})
7
+ if value.blank?
8
+ {}
9
+ elsif value.respond_to?(:to_hash)
10
+ value.to_hash
11
+ elsif value.respond_to?(:to_h)
12
+ value.to_h
13
+ elsif value.respond_to?(:marshal_dump)
14
+ value.marshal_dump
15
+ else
16
+ fail 'value not accepted as a Hash'
17
+ end
18
+ end
19
+
20
+ def self.desserialize(value, options = {})
21
+ value
22
+ end
23
+
24
+ def self.method_missing(method, *args, &block)
25
+ ::Hash.send method, *args, &block
26
+ end
27
+
28
+ end
29
+ end
30
+ end
31
+ end
@@ -1,9 +1,14 @@
1
1
  module Hari
2
2
  module Keys
3
+
4
+ TYPES = %w(string list hash set sorted_set)
5
+
3
6
  autoload :Key, 'hari/keys/key'
7
+ autoload :Hash, 'hari/keys/hash'
4
8
  autoload :List, 'hari/keys/list'
5
9
  autoload :Set, 'hari/keys/set'
6
10
  autoload :SortedSet, 'hari/keys/sorted_set'
7
11
  autoload :String, 'hari/keys/string'
12
+
8
13
  end
9
14
  end
@@ -0,0 +1,67 @@
1
+ module Hari
2
+ module Keys
3
+ class Hash < Key
4
+
5
+ def hash(name = nil)
6
+ return super() unless name
7
+
8
+ @name = name
9
+ self
10
+ end
11
+
12
+ def hash!(name)
13
+ @name = name
14
+ to_h
15
+ end
16
+
17
+ def to_h
18
+ Hari.redis.hgetall key
19
+ end
20
+
21
+ def delete(field)
22
+ Hari.redis.hdel key, field
23
+ end
24
+
25
+ def key?(field)
26
+ Hari.redis.hexists key, field
27
+ end
28
+
29
+ alias :has_key? :key?
30
+ alias :member? :key?
31
+
32
+ def keys
33
+ Hari.redis.hkeys key
34
+ end
35
+
36
+ def values
37
+ Hari.redis.hvals key
38
+ end
39
+
40
+ def values_at(*keys)
41
+ Hari.redis.hmget key, keys
42
+ end
43
+
44
+ def [](field)
45
+ Hari.redis.hget key, field
46
+ end
47
+
48
+ def set(field, value)
49
+ Hari.redis.hset key, field, value
50
+ end
51
+
52
+ alias :[]= :set
53
+
54
+ def merge!(args = {})
55
+ Hari.redis.hmset key, args.to_a.flatten
56
+ end
57
+
58
+ def count
59
+ Hari.redis.hlen key
60
+ end
61
+
62
+ alias :size :count
63
+ alias :length :count
64
+
65
+ end
66
+ end
67
+ end