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