active_redis 0.0.5 → 0.0.7

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 (42) hide show
  1. checksums.yaml +6 -14
  2. data/README.md +9 -5
  3. data/Rakefile +7 -0
  4. data/active_redis.gemspec +3 -0
  5. data/lib/active_redis.rb +9 -0
  6. data/lib/active_redis/associations.rb +25 -24
  7. data/lib/active_redis/associations/association.rb +36 -0
  8. data/lib/active_redis/associations/belongs_to_association.rb +32 -0
  9. data/lib/active_redis/associations/has_many_association.rb +26 -0
  10. data/lib/active_redis/associations/has_one_association.rb +24 -0
  11. data/lib/active_redis/attributes.rb +5 -4
  12. data/lib/active_redis/attributes/integer_attribute.rb +2 -2
  13. data/lib/active_redis/attributes/string_attribute.rb +2 -2
  14. data/lib/active_redis/attributes/time_attribute.rb +2 -2
  15. data/lib/active_redis/base.rb +4 -4
  16. data/lib/active_redis/config.rb +13 -10
  17. data/lib/active_redis/connection_ext/finders_layer.rb +12 -3
  18. data/lib/active_redis/constants.rb +2 -0
  19. data/lib/active_redis/errors.rb +6 -0
  20. data/lib/active_redis/helpers/lua_scripts.rb +7 -0
  21. data/lib/active_redis/inspector.rb +1 -1
  22. data/lib/active_redis/logs/basic_log.rb +11 -0
  23. data/lib/active_redis/logs/console_log.rb +14 -0
  24. data/lib/active_redis/logs/query_logger.rb +29 -0
  25. data/lib/active_redis/lua_scripts/main.lua +127 -50
  26. data/lib/active_redis/naming.rb +2 -0
  27. data/lib/active_redis/persistence.rb +12 -1
  28. data/lib/active_redis/query_chainer.rb +61 -0
  29. data/lib/active_redis/query_executor.rb +24 -0
  30. data/lib/active_redis/query_iterator.rb +27 -0
  31. data/lib/active_redis/railtie.rb +4 -2
  32. data/lib/active_redis/relation.rb +18 -0
  33. data/lib/active_redis/version.rb +1 -1
  34. data/specs/associations_spec.rb +94 -0
  35. data/specs/attributes_spec.rb +221 -0
  36. data/specs/calculations_spec.rb +28 -0
  37. data/specs/config_spec.rb +27 -0
  38. data/specs/inspector_spec.rb +45 -0
  39. data/specs/naming_spec.rb +56 -0
  40. data/specs/test_helper.rb +7 -0
  41. metadata +77 -18
  42. data/lib/active_redis/finders.rb +0 -22
@@ -7,5 +7,7 @@ module ActiveRedis
7
7
 
8
8
  DEFAULT_ATTRIBUTES = {id: :integer, created_at: :time, updated_at: :time}
9
9
 
10
+ ASSOCIATIONS = [:has_one, :has_many, :belongs_to]
11
+
10
12
  end
11
13
  end
@@ -3,10 +3,16 @@ module ActiveRedis
3
3
  class NoConnectionError < ::StandardError
4
4
  end
5
5
 
6
+ class NoLogError < ::StandardError
7
+ end
8
+
6
9
  class NotSpecifiedIdError < ::StandardError
7
10
  end
8
11
 
9
12
  class InvalidArgumentError < ::StandardError
10
13
  end
11
14
 
15
+ class UnregisteredAssociationError < ::StandardError
16
+ end
17
+
12
18
  end
@@ -20,6 +20,13 @@ module ActiveRedis
20
20
  LUA
21
21
  end
22
22
 
23
+ def query_analyzer_script
24
+ <<-LUA
25
+ #{LuaLoader.get_main}
26
+ return query_analyzer(KEYS, ARGV)
27
+ LUA
28
+ end
29
+
23
30
  class LuaLoader
24
31
 
25
32
  def self.get_main
@@ -3,7 +3,7 @@ module ActiveRedis
3
3
 
4
4
  def inspect
5
5
  string = "#<#{self.class.name}:#{self.object_id} "
6
- fields = self.class.attributes_list.map{|field| "#{field}: #{self.send(field)}"}
6
+ fields = self.class.attributes_list.map{|field| val = self.send(field); "#{field}: #{val ? val : 'nil'}"}
7
7
  string << fields.join(", ") << ">"
8
8
  end
9
9
 
@@ -0,0 +1,11 @@
1
+ module ActiveRedis
2
+ module Logs
3
+ class BasicLog
4
+
5
+ def write(message)
6
+ raise NotImplementedError
7
+ end
8
+
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,14 @@
1
+ require_relative './basic_log'
2
+ require 'colorize'
3
+
4
+ module ActiveRedis
5
+ module Logs
6
+ class ConsoleLog < BasicLog
7
+
8
+ def write(message)
9
+ puts message.green
10
+ end
11
+
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,29 @@
1
+ require 'benchmark'
2
+
3
+ module ActiveRedis
4
+ module Logs
5
+ module QueryLogger
6
+
7
+ def loggable(*methods)
8
+ str_eval = ""
9
+ methods.each do |method|
10
+ str_eval += <<-CODE
11
+
12
+ def #{method}_with_loggable(*args)
13
+ res = nil
14
+ time = Benchmark.realtime do
15
+ res = #{method}_without_loggable(*args)
16
+ end
17
+ ActiveRedis.log.write " Redis Query (\#{(time*1000).round(2)}ms) => #{method}(\#{args})"
18
+ res
19
+ end
20
+
21
+ alias_method_chain :#{method}, :loggable
22
+ CODE
23
+ end
24
+ class_eval(str_eval)
25
+ end
26
+
27
+ end
28
+ end
29
+ end
@@ -1,72 +1,149 @@
1
+ -- Split string by pattern
2
+ --
3
+ -- pString - input string
4
+ -- pPattern - pattern for splitting
5
+ --
6
+ -- returns: array of string's parts
7
+ local split = function (pString, pPattern)
8
+ local Table = {} -- NOTE: use {n = 0} in Lua-5.0
9
+ local fpat = "(.-)" .. pPattern
10
+ local last_end = 1
11
+ local s, e, cap = pString:find(fpat, 1)
12
+ while s do
13
+ if s ~= 1 or cap ~= "" then
14
+ table.insert(Table,cap)
15
+ end
16
+ last_end = e+1
17
+ s, e, cap = pString:find(fpat, last_end)
18
+ end
19
+ if last_end <= #pString then
20
+ cap = pString:sub(last_end)
21
+ table.insert(Table, cap)
22
+ end
23
+ return Table
24
+ end
25
+
26
+ -- Fetching keys by pattern
27
+ --
28
+ -- k - pattern for searching
29
+ --
30
+ -- return: list of keys by pattern
1
31
  local keys = function (k)
2
32
  return redis.call("KEYS", k)
3
33
  end
4
34
 
5
- local cond_tonum = function (value, tonum)
6
- if tonum == true then
7
- return tonumber(value)
8
- else
9
- return value
35
+ -- Specify is record match conditions or not
36
+ --
37
+ -- key - record key
38
+ -- argv - array with conditions
39
+ --
40
+ -- return: true if matched or false otherwise
41
+ local is_match_record = function (key, argv)
42
+ for i = 1, #argv, 2 do
43
+ local k = argv[i]
44
+ local value = argv[i+1]
45
+ if (redis.call("HGET", key, k) ~= value) then
46
+ return false
47
+ end
10
48
  end
49
+ return true
11
50
  end
12
51
 
13
- local getHashValues = function (k, attr, tonum)
14
- local result = {}
15
- local ks = keys(k)
16
- for index, key in pairs(ks) do
17
- local cell = redis.call("HGET", key, attr)
18
- table.insert(result, cond_tonum(cell, tonum))
52
+ -- Splitting input string with key=value to array
53
+ --
54
+ -- str - string like "attr1=value1, attr2=value2"
55
+ --
56
+ -- return: array
57
+ local fetch_conditions = function(str)
58
+ local t = {}
59
+ for i, value in pairs(split(str, ",")) do
60
+ local res = split(value, "=")
61
+ table.insert(t, res[1])
62
+ table.insert(t, res[2])
19
63
  end
20
- return result
64
+ return t
21
65
  end
22
66
 
23
- local calculate_count = function (key)
24
- return #keys(key)
25
- end
26
-
27
- local calculate_pluck = function (key, attr)
28
- return getHashValues(key, attr, false)
29
- end
30
-
31
- local calculate_max = function (key, attr)
32
- local res = getHashValues(key, attr, true)
33
- table.sort(res)
34
- return res[#res]
67
+ -- Applying where conditions for table set
68
+ --
69
+ -- key - table name
70
+ -- cond_str - conditions represented as string
71
+ --
72
+ -- return: key of result id list
73
+ local apply_where = function(key, cond_str, seed)
74
+ math.randomseed(seed)
75
+ local cond_array = fetch_conditions(cond_str)
76
+ local ks = keys(key)
77
+ local list_name = "templist:"..math.random()
78
+ for index, k in pairs(ks) do
79
+ if is_match_record(k, cond_array) == true then
80
+ redis.call("RPUSH", list_name, redis.call("HGET", k, "id"))
81
+ end
82
+ end
83
+ return list_name
35
84
  end
36
85
 
37
- local calculate_min = function (key, attr)
38
- local res = getHashValues(key, attr, true)
39
- table.sort(res)
40
- return res[1]
86
+ -- Applying order conditions to command string
87
+ --
88
+ -- key - table name
89
+ -- command - current query to redis
90
+ -- cond_str - conditions represented as string
91
+ --
92
+ -- returns: changed command string with sorting by
93
+ local apply_order = function(key, command, cond_str)
94
+ table.insert(command, 'BY')
95
+ if string.len(cond_str) == 0 then
96
+ table.insert(command, 'nosort')
97
+ else
98
+ local cond_array = fetch_conditions(cond_str)
99
+ table.insert(command, key.."->"..cond_array[1])
100
+ table.insert(command, "ALPHA")
101
+ table.insert(command, cond_array[2])
102
+ end
103
+ return command
41
104
  end
42
105
 
43
- local calculate_sum = function (key, attr)
44
- local res = getHashValues(key, attr, true)
45
- local sum = 0
46
- for index, s in pairs(res) do
47
- sum = sum + s
106
+ -- Applying limit conditions to command string
107
+ --
108
+ -- command - current query to redis
109
+ -- cond_str - conditions represented as string
110
+ --
111
+ -- returns: changed command string with limit
112
+ local apply_limit = function(command, cond_str)
113
+ if string.len(cond_str) ~= 0 then
114
+ local cond_array = fetch_conditions(cond_str)
115
+ table.insert(command, "LIMIT")
116
+ table.insert(command, cond_array[1])
117
+ table.insert(command, cond_array[2])
48
118
  end
49
- return sum
119
+ return command
50
120
  end
51
121
 
52
- local is_match_record = function (key, argv)
53
- for i = 1, #argv, 2 do
54
- local k = argv[i]
55
- local value = argv[i+1]
56
- if (redis.call("HGET", key, k) ~= value) then
57
- return false
58
- end
59
- end
60
- return true
122
+ -- Fetching record by table name and id
123
+ --
124
+ -- key - table name
125
+ -- id - record id
126
+ --
127
+ -- returns: record hash
128
+ local fetch_row = function(key, id)
129
+ local name = string.sub(key, 1, -2)
130
+ name = name..id
131
+ return redis.call("HGETALL", name)
61
132
  end
62
133
 
63
- local where_finder = function (key, argv)
134
+ -- Query analyzer for searching
135
+ --
136
+ -- key - table name
137
+ -- argv - array with query parameters WHERE, ORDER, LIMIT
138
+ local query_analyzer = function (keys, argv)
139
+ local temp_list = apply_where(keys[1], argv[1], tonumber(keys[2]))
140
+ local sort_command = {"SORT", temp_list}
141
+ sort_command = apply_order(keys[1], sort_command, argv[2])
142
+ sort_command = apply_limit(sort_command, argv[3])
143
+ local ids = redis.call(unpack(sort_command))
64
144
  local result = {}
65
- local ks = keys(key)
66
- for index, k in pairs(ks) do
67
- if is_match_record(k, argv) == true then
68
- table.insert(result, redis.call("HGETALL", k))
69
- end
145
+ for index, id in pairs(ids) do
146
+ table.insert(result, fetch_row(keys[1], id))
70
147
  end
71
148
  return result
72
- end
149
+ end
@@ -1,3 +1,5 @@
1
+ require 'active_support/core_ext/string'
2
+
1
3
  module ActiveRedis
2
4
 
3
5
  module Naming
@@ -22,6 +22,17 @@ module ActiveRedis
22
22
  self.save
23
23
  end
24
24
 
25
+ def reload
26
+ if self.class.respond_to? :associations
27
+ self.class.associations.each { |key, assoc| assoc.reload(self) }
28
+ end
29
+ true
30
+ end
31
+
32
+ def touch
33
+ save
34
+ end
35
+
25
36
  private
26
37
 
27
38
  def fill_attributes
@@ -33,7 +44,7 @@ module ActiveRedis
33
44
  def prepare_hash
34
45
  fill_attributes
35
46
  self.class.attributes_list.inject({}) do |hash, attribute|
36
- hash[attribute.to_sym] = self.send("#{attribute}"); hash
47
+ hash[attribute.to_sym] = self.instance_variable_get("@#{attribute}"); hash
37
48
  end
38
49
  end
39
50
 
@@ -0,0 +1,61 @@
1
+ require 'active_redis/relation'
2
+ require 'active_redis/query_iterator'
3
+
4
+ module ActiveRedis
5
+ autoload :QueryExecutor, 'active_redis/query_executor'
6
+
7
+ class QueryChainer
8
+ include Relation
9
+ include QueryIterator
10
+
11
+ def initialize(target)
12
+ @where_options = {}
13
+ @order_options = {id: :asc}
14
+ @limit_options = {per_page: 10, page: 0}
15
+ @target = target
16
+ end
17
+
18
+ def apply_where(options)
19
+ @where_options.merge!(options)
20
+ self
21
+ end
22
+
23
+ def apply_order(options)
24
+ @order_options = options if options.any?
25
+ self
26
+ end
27
+
28
+ def apply_limit(options)
29
+ @limit_options = options if options.any?
30
+ self
31
+ end
32
+
33
+ def apply_top(options)
34
+ apply_limit per_page: 1, page: 0
35
+ end
36
+
37
+ def reload
38
+ @collection = nil
39
+ end
40
+
41
+ def linked_objects
42
+ @collection ||= objects_by_query
43
+ end
44
+
45
+ private
46
+
47
+ def execute_query
48
+ QueryExecutor.execute(@target, @where_options, @order_options, @limit_options)
49
+ end
50
+
51
+ def objects_by_query
52
+ res = execute_query.inject([]) { |arr, attrs| arr << @target.new(attrs) if attrs && attrs.any?; arr }
53
+ top_limit? ? res.first : res
54
+ end
55
+
56
+ def top_limit?
57
+ @limit_options[:per_page] == 1 && @limit_options[:page] == 0
58
+ end
59
+
60
+ end
61
+ end
@@ -0,0 +1,24 @@
1
+ module ActiveRedis
2
+ class QueryExecutor
3
+
4
+ def self.execute(target, where, order, limit)
5
+ ActiveRedis.connection.run_query_analyzer target, [prepare_where(where), prepare_order(order), prepare_limit(limit)]
6
+ end
7
+
8
+ private
9
+
10
+ def self.prepare_where(where)
11
+ where.keys.map{|key| "#{key}=#{where[key]}" }.join(",")
12
+ end
13
+
14
+ def self.prepare_order(order)
15
+ field = order.keys.first
16
+ "#{field}=#{order[field]}"
17
+ end
18
+
19
+ def self.prepare_limit(limit)
20
+ limit.any? ? "#{limit[:page]}=#{limit[:per_page]}" : ""
21
+ end
22
+
23
+ end
24
+ end
@@ -0,0 +1,27 @@
1
+ module ActiveRedis
2
+ module QueryIterator
3
+ include Enumerable
4
+
5
+ def method_missing(method, *args)
6
+ unless linked_objects.is_a? Array
7
+ linked_objects.send method, *args
8
+ else
9
+ super
10
+ end
11
+ end
12
+
13
+ def inspect
14
+ if linked_objects.is_a? Array
15
+ "[#{linked_objects.map{|e| e.inspect}.join(', ')}]"
16
+ else
17
+ linked_objects.inspect
18
+ end
19
+ end
20
+
21
+ def each(&block)
22
+ raise "Exception occured when trying call #each on #{@target}" unless linked_objects.is_a? Array
23
+ linked_objects.each(&block)
24
+ end
25
+
26
+ end
27
+ end