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.
- checksums.yaml +6 -14
- data/README.md +9 -5
- data/Rakefile +7 -0
- data/active_redis.gemspec +3 -0
- data/lib/active_redis.rb +9 -0
- data/lib/active_redis/associations.rb +25 -24
- data/lib/active_redis/associations/association.rb +36 -0
- data/lib/active_redis/associations/belongs_to_association.rb +32 -0
- data/lib/active_redis/associations/has_many_association.rb +26 -0
- data/lib/active_redis/associations/has_one_association.rb +24 -0
- data/lib/active_redis/attributes.rb +5 -4
- data/lib/active_redis/attributes/integer_attribute.rb +2 -2
- data/lib/active_redis/attributes/string_attribute.rb +2 -2
- data/lib/active_redis/attributes/time_attribute.rb +2 -2
- data/lib/active_redis/base.rb +4 -4
- data/lib/active_redis/config.rb +13 -10
- data/lib/active_redis/connection_ext/finders_layer.rb +12 -3
- data/lib/active_redis/constants.rb +2 -0
- data/lib/active_redis/errors.rb +6 -0
- data/lib/active_redis/helpers/lua_scripts.rb +7 -0
- data/lib/active_redis/inspector.rb +1 -1
- data/lib/active_redis/logs/basic_log.rb +11 -0
- data/lib/active_redis/logs/console_log.rb +14 -0
- data/lib/active_redis/logs/query_logger.rb +29 -0
- data/lib/active_redis/lua_scripts/main.lua +127 -50
- data/lib/active_redis/naming.rb +2 -0
- data/lib/active_redis/persistence.rb +12 -1
- data/lib/active_redis/query_chainer.rb +61 -0
- data/lib/active_redis/query_executor.rb +24 -0
- data/lib/active_redis/query_iterator.rb +27 -0
- data/lib/active_redis/railtie.rb +4 -2
- data/lib/active_redis/relation.rb +18 -0
- data/lib/active_redis/version.rb +1 -1
- data/specs/associations_spec.rb +94 -0
- data/specs/attributes_spec.rb +221 -0
- data/specs/calculations_spec.rb +28 -0
- data/specs/config_spec.rb +27 -0
- data/specs/inspector_spec.rb +45 -0
- data/specs/naming_spec.rb +56 -0
- data/specs/test_helper.rb +7 -0
- metadata +77 -18
- data/lib/active_redis/finders.rb +0 -22
data/lib/active_redis/errors.rb
CHANGED
@@ -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
|
@@ -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}: #{
|
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,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
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
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
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
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
|
64
|
+
return t
|
21
65
|
end
|
22
66
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
local
|
32
|
-
local
|
33
|
-
|
34
|
-
|
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
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
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
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
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
|
119
|
+
return command
|
50
120
|
end
|
51
121
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
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
|
-
|
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
|
-
|
66
|
-
|
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
|
data/lib/active_redis/naming.rb
CHANGED
@@ -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.
|
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
|