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