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.
Files changed (34) hide show
  1. data/Gemfile +4 -1
  2. data/README.markdown +62 -27
  3. data/Rakefile +0 -6
  4. data/VERSION +1 -1
  5. data/lib/redis-model-extension/attributes.rb +38 -0
  6. data/lib/redis-model-extension/autoincrement_id.rb +38 -0
  7. data/lib/redis-model-extension/config.rb +67 -0
  8. data/lib/redis-model-extension/get_find.rb +146 -0
  9. data/lib/redis-model-extension/initialize.rb +179 -0
  10. data/lib/redis-model-extension/old_initialize.rb +103 -0
  11. data/lib/redis-model-extension/redis_key.rb +119 -0
  12. data/lib/redis-model-extension/save_destroy.rb +104 -0
  13. data/lib/redis-model-extension/store_keys.rb +34 -0
  14. data/lib/redis-model-extension/validation.rb +84 -0
  15. data/lib/redis-model-extension/value_transform.rb +51 -0
  16. data/lib/redis-model-extension.rb +52 -320
  17. data/lib/redis-model.rb +14 -0
  18. data/redis-model-extension.gemspec +40 -6
  19. data/test/helper.rb +17 -2
  20. data/test/models.rb +83 -0
  21. data/test/redis_model_old/test_redis_model_old_config.rb +218 -0
  22. data/test/redis_model_parts/test_attributes.rb +34 -0
  23. data/test/redis_model_parts/test_autoincrement_id.rb +86 -0
  24. data/test/redis_model_parts/test_dynamic_alias.rb +147 -0
  25. data/test/redis_model_parts/test_get_find.rb +111 -0
  26. data/test/redis_model_parts/test_hooks.rb +36 -0
  27. data/test/redis_model_parts/test_initialize.rb +46 -0
  28. data/test/redis_model_parts/test_redis_key.rb +119 -0
  29. data/test/redis_model_parts/test_save_destroy.rb +102 -0
  30. data/test/redis_model_parts/test_validation.rb +72 -0
  31. data/test/redis_model_parts/test_variable_types.rb +91 -0
  32. data/test/test_redis-model-extension.rb +38 -200
  33. data/test/test_string_to_bool.rb +30 -0
  34. 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