active_redis 0.0.5 → 0.0.7

Sign up to get free protection for your applications and to get access to all the features.
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