redis-model-extension 0.3.8 → 0.4.0
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.
- data/Gemfile +4 -1
- data/README.markdown +62 -27
- data/Rakefile +0 -6
- data/VERSION +1 -1
- data/lib/redis-model-extension/attributes.rb +38 -0
- data/lib/redis-model-extension/autoincrement_id.rb +38 -0
- data/lib/redis-model-extension/config.rb +67 -0
- data/lib/redis-model-extension/get_find.rb +146 -0
- data/lib/redis-model-extension/initialize.rb +179 -0
- data/lib/redis-model-extension/old_initialize.rb +103 -0
- data/lib/redis-model-extension/redis_key.rb +119 -0
- data/lib/redis-model-extension/save_destroy.rb +104 -0
- data/lib/redis-model-extension/store_keys.rb +34 -0
- data/lib/redis-model-extension/validation.rb +84 -0
- data/lib/redis-model-extension/value_transform.rb +51 -0
- data/lib/redis-model-extension.rb +52 -320
- data/lib/redis-model.rb +14 -0
- data/redis-model-extension.gemspec +40 -6
- data/test/helper.rb +17 -2
- data/test/models.rb +83 -0
- data/test/redis_model_old/test_redis_model_old_config.rb +218 -0
- data/test/redis_model_parts/test_attributes.rb +34 -0
- data/test/redis_model_parts/test_autoincrement_id.rb +86 -0
- data/test/redis_model_parts/test_dynamic_alias.rb +147 -0
- data/test/redis_model_parts/test_get_find.rb +111 -0
- data/test/redis_model_parts/test_hooks.rb +36 -0
- data/test/redis_model_parts/test_initialize.rb +46 -0
- data/test/redis_model_parts/test_redis_key.rb +119 -0
- data/test/redis_model_parts/test_save_destroy.rb +102 -0
- data/test/redis_model_parts/test_validation.rb +72 -0
- data/test/redis_model_parts/test_variable_types.rb +91 -0
- data/test/test_redis-model-extension.rb +38 -200
- data/test/test_string_to_bool.rb +30 -0
- metadata +90 -32
@@ -0,0 +1,103 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
module RedisModelExtension
|
3
|
+
|
4
|
+
# == Old Initialize
|
5
|
+
# port for old initialize method to new structure
|
6
|
+
module ClassOldInitialize
|
7
|
+
TYPE_TRANSLATIONS = {
|
8
|
+
:integer => :to_i,
|
9
|
+
:string => :to_s,
|
10
|
+
:bool => :to_bool,
|
11
|
+
:symbol => :to_sym,
|
12
|
+
:array => :to_array,
|
13
|
+
:hash => :to_hash,
|
14
|
+
:time => :to_time,
|
15
|
+
:date => :to_date
|
16
|
+
}
|
17
|
+
# old method to initialize redis model extenstion
|
18
|
+
# Usage:
|
19
|
+
# REDIS_MODEL_CONF = {
|
20
|
+
# :fields => {
|
21
|
+
# :integer => :to_i,
|
22
|
+
# :boolean => :to_bool,
|
23
|
+
# :string => :to_s,
|
24
|
+
# :symbol => :to_sym,
|
25
|
+
# },
|
26
|
+
# :required => [:integer, :string],
|
27
|
+
# :redis_key => [:string, :symbol],
|
28
|
+
# :redis_aliases => {
|
29
|
+
# :token => [:symbol]
|
30
|
+
# },
|
31
|
+
# # (default is true) if true all nil values will not be saved into redis,
|
32
|
+
# # there should be problem when you want to set some value to nil and same
|
33
|
+
# # it will not be saved (use false to prevent this)
|
34
|
+
# :reject_nil_values => false
|
35
|
+
# }
|
36
|
+
# include RedisModel
|
37
|
+
# initialize_redis_model_methods REDIS_MODEL_CONF
|
38
|
+
def initialize_redis_model_methods conf
|
39
|
+
puts "WARNING: This initilization method is deprecated and will be removed in future! \n Please read documentation how to change your model to use new initialization methods"
|
40
|
+
|
41
|
+
@conf = {:reject_nil_values => true}.merge(conf)
|
42
|
+
#take all fields and make methods for them
|
43
|
+
conf[:fields].each do |name, action|
|
44
|
+
redis_fields_config[name] = TYPE_TRANSLATIONS.invert[action]
|
45
|
+
redis_fields_defaults_config[name] = nil
|
46
|
+
|
47
|
+
# define getter method for field
|
48
|
+
define_method "#{name}" do
|
49
|
+
value_get name
|
50
|
+
end
|
51
|
+
|
52
|
+
# define setter method for field
|
53
|
+
define_method "#{name}=" do |new_value|
|
54
|
+
value_set name, new_value
|
55
|
+
end
|
56
|
+
|
57
|
+
# define exists? method for field
|
58
|
+
define_method "#{name}?" do
|
59
|
+
value_get(name) && !value_get(name).blank? ? true : false
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# save nil values?
|
64
|
+
redis_save_fields_with_nil false if !conf.has_key?(:reject_nil_values) || conf[:reject_nil_values] == true
|
65
|
+
|
66
|
+
# save into class config about redis key
|
67
|
+
@redis_key_config = conf[:redis_key]
|
68
|
+
|
69
|
+
#validate presence of all fields in key
|
70
|
+
@required_config = (@redis_key_config | conf[:required])
|
71
|
+
(@redis_key_config | conf[:required]).each do |field|
|
72
|
+
validates field, :presence => :true
|
73
|
+
end
|
74
|
+
|
75
|
+
# save into class config about redis keys
|
76
|
+
@redis_alias_config = {}
|
77
|
+
conf[:redis_aliases].each do |key, fields|
|
78
|
+
@redis_alias_config[key] = {
|
79
|
+
main_fields: fields,
|
80
|
+
order_field: nil,
|
81
|
+
args_field: nil,
|
82
|
+
}
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# get config hash
|
87
|
+
def conf
|
88
|
+
fields = {}
|
89
|
+
redis_fields_config.each do |key, type|
|
90
|
+
fields[key] = TYPE_TRANSLATIONS[type] if TYPE_TRANSLATIONS.has_key?(type)
|
91
|
+
end
|
92
|
+
{
|
93
|
+
fields: fields,
|
94
|
+
required: @required_config.sort,
|
95
|
+
redis_key: redis_key_config,
|
96
|
+
redis_aliases: redis_alias_config.inject({}){|o,(k,v)| o[k] = v[:main_fields]; o},
|
97
|
+
reject_nil_values: !redis_save_fields_with_nil_conf,
|
98
|
+
}
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
module RedisModelExtension
|
3
|
+
|
4
|
+
# == Class Redis Key
|
5
|
+
# generators of redis keys
|
6
|
+
# used for creating keys for search and save of models & aliases
|
7
|
+
#
|
8
|
+
module ClassRedisKey
|
9
|
+
|
10
|
+
# Generates redis key for storing object
|
11
|
+
# * will produce something like: your_class:key:field_value1:field_value2...
|
12
|
+
# (depending on your redis_key setting)
|
13
|
+
def generate_key args = {}, key = "key"
|
14
|
+
#normalize input hash of arguments
|
15
|
+
args = HashWithIndifferentAccess.new(args)
|
16
|
+
|
17
|
+
out = "#{self.name.to_s.underscore.to_sym}:#{key}"
|
18
|
+
redis_key_config.each do |key|
|
19
|
+
out += add_item_to_redis_key args, key
|
20
|
+
end
|
21
|
+
out
|
22
|
+
end
|
23
|
+
|
24
|
+
|
25
|
+
# Generates redis key for storing indexes for dynamic aliases
|
26
|
+
# will produce something like: your_class:dynamic:name_of_your_dynami_alias:field_value2:field_value1...
|
27
|
+
# (field values are sorted by fields order)
|
28
|
+
# * dynamic_alias_name (Symbol) - name of your dynamic alias
|
29
|
+
# * args (Hash) - arguments of your model
|
30
|
+
# * field_order (Array of symbols) - order of fields (ex. [:field2, :field1])
|
31
|
+
# * field_args (Hash) - hash of values for aliasing (ex. {:field1 => "field_value1", :field2 => "field_value2"})
|
32
|
+
def generate_alias_key alias_name, args = {}
|
33
|
+
#check if asked dynamic alias exists
|
34
|
+
raise ArgumentError, "Unknown dynamic alias: '#{alias_name}', use: #{redis_alias_config.keys.join(", ")} " unless redis_alias_config.has_key?(alias_name.to_sym)
|
35
|
+
|
36
|
+
#normalize input hash of arguments
|
37
|
+
args = HashWithIndifferentAccess.new(args)
|
38
|
+
|
39
|
+
# prepare class name + dynamic + alias name
|
40
|
+
out = "#{self.name.to_s.underscore.to_sym}:alias:#{alias_name}"
|
41
|
+
|
42
|
+
#get config
|
43
|
+
config = redis_alias_config[alias_name.to_sym]
|
44
|
+
|
45
|
+
# use all specified keys
|
46
|
+
config[:main_fields].each do |key|
|
47
|
+
out += add_item_to_redis_key args, key
|
48
|
+
end
|
49
|
+
|
50
|
+
#is alias dynamic?
|
51
|
+
if config[:order_field] && config[:args_field]
|
52
|
+
#check if input arguments has order field
|
53
|
+
if args.has_key?(config[:order_field]) && args[config[:order_field]] && args.has_key?(config[:args_field]) && args[config[:args_field]]
|
54
|
+
#use filed order from defined field in args
|
55
|
+
args[config[:order_field]].each do |key|
|
56
|
+
out += add_item_to_redis_key args[config[:args_field]], key
|
57
|
+
end
|
58
|
+
else
|
59
|
+
# use global search
|
60
|
+
out += ":*"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
out
|
65
|
+
end
|
66
|
+
|
67
|
+
# Check if key by arguments exists in db
|
68
|
+
def exists? args = {}
|
69
|
+
RedisModelExtension::Database.redis.exists(self.name.constantize.generate_key(args))
|
70
|
+
end
|
71
|
+
|
72
|
+
#Check if key by alias name and arguments exists in db
|
73
|
+
def alias_exists? alias_name, args = {}
|
74
|
+
RedisModelExtension::Database.redis.exists(self.name.constantize.generate_alias_key(alias_name, args))
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
|
79
|
+
# return one item of redis key (will decide to input value or to add * for search)
|
80
|
+
def add_item_to_redis_key args, key
|
81
|
+
if args.has_key?(key) && !args[key].nil?
|
82
|
+
key = ":#{args[key]}"
|
83
|
+
key = key.mb_chars.downcase if redis_key_normalize_conf.include?(:downcase)
|
84
|
+
key = ActiveSupport::Inflector::transliterate(key) if redis_key_normalize_conf.include?(:transliterate)
|
85
|
+
key
|
86
|
+
else
|
87
|
+
":*"
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
92
|
+
|
93
|
+
# == Redis Key
|
94
|
+
# aliases for instance to class generators of redis keys
|
95
|
+
#
|
96
|
+
module RedisKey
|
97
|
+
|
98
|
+
# get redis key for instance
|
99
|
+
def redis_key
|
100
|
+
self.class.generate_key(to_arg)
|
101
|
+
end
|
102
|
+
|
103
|
+
# get redis key for instance alias
|
104
|
+
def redis_alias_key alias_name
|
105
|
+
self.class.generate_alias_key(alias_name, to_arg)
|
106
|
+
end
|
107
|
+
|
108
|
+
# pointer to exists?
|
109
|
+
def exists?
|
110
|
+
self.class.exists? to_arg
|
111
|
+
end
|
112
|
+
|
113
|
+
# pointer to alias_exists?
|
114
|
+
def alias_exists? alias_name
|
115
|
+
self.class.alias_exists? alias_name, to_arg
|
116
|
+
end
|
117
|
+
|
118
|
+
end
|
119
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
module RedisModelExtension
|
3
|
+
|
4
|
+
module ClassCreate
|
5
|
+
|
6
|
+
# create instance and save it
|
7
|
+
def create args = {}
|
8
|
+
instance = self.new(args)
|
9
|
+
instance.save
|
10
|
+
return instance
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
14
|
+
|
15
|
+
module SaveDestroy
|
16
|
+
|
17
|
+
# save method - save all attributes (fields) and create aliases
|
18
|
+
def save
|
19
|
+
# can be saved into redis?
|
20
|
+
if valid?
|
21
|
+
perform = lambda do
|
22
|
+
#autoicrement id
|
23
|
+
self.send("id=", increment_id) if redis_key_config.include?(:id) && !self.id?
|
24
|
+
|
25
|
+
#generate key (possibly new)
|
26
|
+
generated_key = redis_key
|
27
|
+
|
28
|
+
#take care about renaming saved hash in redis (if key changed)
|
29
|
+
if redis_old_keys[:key] && redis_old_keys[:key] != generated_key && RedisModelExtension::Database.redis.exists(redis_old_keys[:key])
|
30
|
+
RedisModelExtension::Database.redis.rename(redis_old_keys[:key], generated_key)
|
31
|
+
end
|
32
|
+
|
33
|
+
#ignore nil values for save
|
34
|
+
args = self.class.redis_save_fields_with_nil_conf ? to_arg : to_arg.reject{|k,v| v.nil?}
|
35
|
+
|
36
|
+
#perform save to redis hash
|
37
|
+
RedisModelExtension::Database.redis.hmset(generated_key, *args.inject([]){ |arr,kv| arr + [kv[0], value_to_redis(kv[0], kv[1])]})
|
38
|
+
|
39
|
+
#destroy aliases
|
40
|
+
destroy_aliases!
|
41
|
+
create_aliases
|
42
|
+
|
43
|
+
#after save make sure instance remember old key to know if it needs to be ranamed
|
44
|
+
store_keys
|
45
|
+
end
|
46
|
+
|
47
|
+
run_callbacks :save do
|
48
|
+
unless exists?
|
49
|
+
run_callbacks :create do
|
50
|
+
perform.()
|
51
|
+
end
|
52
|
+
else
|
53
|
+
perform.()
|
54
|
+
end
|
55
|
+
end
|
56
|
+
return self
|
57
|
+
else
|
58
|
+
return false
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# create aliases (create key value [STRING] key is alias redis key and value is redis key)
|
63
|
+
def create_aliases
|
64
|
+
main_key = redis_key
|
65
|
+
redis_alias_config.each do |alias_name, fields|
|
66
|
+
RedisModelExtension::Database.redis.set(redis_alias_key(alias_name), main_key) if valid_alias_key? alias_name
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# update multiple attrubutes at once
|
71
|
+
def update args
|
72
|
+
args.each do |key, value|
|
73
|
+
method = "#{key}=".to_sym
|
74
|
+
if self.respond_to? method
|
75
|
+
self.send(method, value)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
#remove record form database
|
81
|
+
def destroy!
|
82
|
+
if exists?
|
83
|
+
run_callbacks :destroy do
|
84
|
+
#destroy main object
|
85
|
+
RedisModelExtension::Database.redis.del(redis_key)
|
86
|
+
destroy_aliases!
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
alias :destroy :destroy!
|
92
|
+
|
93
|
+
# remove all aliases
|
94
|
+
def destroy_aliases!
|
95
|
+
#do it only if it is existing object!
|
96
|
+
if redis_old_keys[:aliases].size > 0
|
97
|
+
redis_old_keys[:aliases].each do |alias_key|
|
98
|
+
RedisModelExtension::Database.redis.del alias_key
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
module RedisModelExtension
|
3
|
+
module StoreKeys
|
4
|
+
|
5
|
+
# store old arguments, need's to be called in find/get initialization
|
6
|
+
# will remember old arguments and remember redis keys
|
7
|
+
# if some fileds in redis key will change, then do rename
|
8
|
+
# without this you can end up with old and new saved object!
|
9
|
+
def store_keys
|
10
|
+
store_redis_keys
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
# set old arguments
|
16
|
+
def store_redis_keys
|
17
|
+
args = to_arg
|
18
|
+
#store main key
|
19
|
+
redis_old_keys[:key] = self.class.generate_key(args) #store main key
|
20
|
+
|
21
|
+
#store alias keys
|
22
|
+
redis_old_keys[:aliases] = []
|
23
|
+
redis_alias_config.each do |alias_name, fields|
|
24
|
+
redis_old_keys[:aliases] << redis_alias_key(alias_name) if valid_alias_key? alias_name
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# get old arguments
|
29
|
+
def redis_old_keys
|
30
|
+
@redis_old_keys ||= {:key => nil, :aliases => []}
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
module RedisModelExtension
|
3
|
+
|
4
|
+
module ClassValidations
|
5
|
+
|
6
|
+
# Validates if key by arguments is valid
|
7
|
+
# (all needed fields are not nil!)
|
8
|
+
def valid_key? args = {}
|
9
|
+
#normalize input hash of arguments
|
10
|
+
args = HashWithIndifferentAccess.new(args)
|
11
|
+
|
12
|
+
redis_key_config.each do |key|
|
13
|
+
return false unless valid_item_for_redis_key? args, key
|
14
|
+
end
|
15
|
+
return true
|
16
|
+
end
|
17
|
+
|
18
|
+
|
19
|
+
# Validates if key by alias name and arguments is valid
|
20
|
+
# (all needed fields are not nil!)
|
21
|
+
def valid_alias_key? alias_name, args = {}
|
22
|
+
raise ArgumentError, "Unknown dynamic alias, use: #{redis_alias_config.keys.join(", ")}" unless redis_alias_config.has_key?(alias_name.to_sym)
|
23
|
+
|
24
|
+
#normalize input hash of arguments
|
25
|
+
args = HashWithIndifferentAccess.new(args)
|
26
|
+
|
27
|
+
config = redis_alias_config[alias_name.to_sym]
|
28
|
+
|
29
|
+
|
30
|
+
# use all specified keys
|
31
|
+
config[:main_fields].each do |key|
|
32
|
+
return false unless valid_item_for_redis_key? args, key
|
33
|
+
end
|
34
|
+
|
35
|
+
# is dynamic alias?
|
36
|
+
if config[:order_field] && config[:args_field]
|
37
|
+
#check if input arguments has order field
|
38
|
+
if args.has_key?(config[:order_field]) && args[config[:order_field]] && args.has_key?(config[:args_field]) && args[config[:args_field]]
|
39
|
+
#use filed order from defined field in args
|
40
|
+
args[config[:order_field]].each do |key|
|
41
|
+
return false unless valid_item_for_redis_key? args[config[:args_field]], key
|
42
|
+
end
|
43
|
+
else
|
44
|
+
return false
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
return true
|
49
|
+
end
|
50
|
+
|
51
|
+
# validate one item of redis key
|
52
|
+
def valid_item_for_redis_key? args, key
|
53
|
+
(args.has_key?(key) && !args[key].nil?) || redis_fields_config[key] == :autoincrement
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
#look for bad cofiguration in redis key and raise argument error
|
59
|
+
def validate_redis_key
|
60
|
+
valid_fields = redis_fields_config.select{|k,v| v != :array && v != :hash }.keys
|
61
|
+
bad_fields = redis_key_config - valid_fields
|
62
|
+
raise ArgumentError, "Sorry, but you cannot use as redis key [nonexisting | array | hash] fields: [#{bad_fields.join(",")}], availible are: #{valid_fields.join(", ")}" unless bad_fields.size == 0
|
63
|
+
end
|
64
|
+
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
module Validations
|
69
|
+
|
70
|
+
#pointer to validation
|
71
|
+
def valid_key?
|
72
|
+
self.class.valid_key? to_arg
|
73
|
+
end
|
74
|
+
|
75
|
+
#pointer to validation
|
76
|
+
def valid_alias_key? alias_name
|
77
|
+
self.class.valid_alias_key? alias_name, to_arg
|
78
|
+
end
|
79
|
+
|
80
|
+
def error
|
81
|
+
self.errors
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
module RedisModelExtension
|
3
|
+
module ValueTransform
|
4
|
+
|
5
|
+
# choose right type of value and then transform it for redis
|
6
|
+
def value_to_redis name, value
|
7
|
+
if redis_fields_config.has_key?(name)
|
8
|
+
value_transform value, redis_fields_config[name]
|
9
|
+
else
|
10
|
+
value
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
# convert value for valid format which can be saved in redis
|
15
|
+
def value_transform value, type
|
16
|
+
return nil if value.nil? || value.to_s.size == 0
|
17
|
+
case type
|
18
|
+
when :integer then value.to_i
|
19
|
+
when :autoincrement then value.to_i
|
20
|
+
when :string then value.to_s
|
21
|
+
when :float then value.to_f
|
22
|
+
when :bool then value.to_s
|
23
|
+
when :symbol then value.to_s
|
24
|
+
when :array then value.to_json
|
25
|
+
when :hash then value.to_json
|
26
|
+
when :time then Time.parse(value.to_s).strftime("%Y.%m.%d %H:%M:%S")
|
27
|
+
when :date then Date.parse(value.to_s).strftime("%Y-%m-%d")
|
28
|
+
else value
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# convert value from redis into valid format in ruby
|
33
|
+
def value_parse value, type
|
34
|
+
return nil if value.nil? || value.to_s.size == 0
|
35
|
+
case type
|
36
|
+
when :integer then value.to_i
|
37
|
+
when :autoincrement then value.to_i
|
38
|
+
when :string then value.to_s
|
39
|
+
when :float then value.to_f
|
40
|
+
when :bool then value.to_s.to_bool
|
41
|
+
when :symbol then value.to_s.to_sym
|
42
|
+
when :array then value.is_a?(String) ? JSON.parse(value) : value
|
43
|
+
when :hash then value.is_a?(String) ? JSON.parse(value) : value
|
44
|
+
when :time then value.is_a?(String) ? Time.parse(value) : value
|
45
|
+
when :date then value.is_a?(String) ? Date.parse(value) : value
|
46
|
+
else value
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
end
|