redis_storage_methods 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in redis_storage_methods.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Jordan Prince
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,70 @@
1
+ # RedisStorageMethods
2
+
3
+ RedisStorageMethods allows you to include a MixIn to your models that will let you easily create copies in a redis store when you create actual objects with no extra logic, and expose methods that enable you to find in redis store, not in the database, to protect yourself from the massive time costs in querying with SQL compared to Redis.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'redis_storage_methods'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install redis_storage_methods
18
+
19
+ ## Usage
20
+
21
+ Pretty easy here, just do:
22
+
23
+ class Battle < ActiveRecord::Base
24
+ include RedisStorageMethods
25
+ ...
26
+ end
27
+
28
+ Once you've done this, you'll need to add a few methods to Battle that the Mixin requires to be defined(even empty methods will do).
29
+
30
+ #accepts the part of the form hash with nested atributes(but with attributes taken off keys), like { "videos" => {"0" => {field => value}}}
31
+ #and then creates models. Because we don't support nested creation at the meta level, must be handled locally.
32
+ def create_associations_from_params(params)
33
+ end
34
+
35
+ #No nesting in Redis, no methods for listing associations on DataMapper/AR, This is custom, returns nested objects for use in storage
36
+ #with redis. Because I dont want to write metacode that detects nested objects within my own nested objects, I'm just leaving it
37
+ # to each model to implement this in a custom fashion.
38
+ def add_key_value_pairs_of_associations_for_redis!(array)
39
+ end
40
+
41
+ #expects to get hash of things like video0video_id => "555" from redis, needs to create associations from it.
42
+ def populate_associations_from_redis(redis_hash)
43
+ end
44
+
45
+ #if you have sets stored outside the redis hash, need to make a call to get them, haven't you?
46
+ # you can do that here, or leave this a nil method. It won't hurt you.
47
+ def populate_custom_fields_from_extraneous_redis_calls
48
+ end
49
+
50
+ #method to sync object in db with redis.
51
+ def sync_with_redis
52
+ end
53
+
54
+ #create the custom fields you need like lists for arrays etc, artist:name => id references,
55
+ #stuff like that.
56
+ def create_custom_redis_fields
57
+ end
58
+
59
+ def after_redis_create_do
60
+ end
61
+
62
+ Copy and paste these bad boys into the Battle class. You should be aware that nested associations are not stored in redis by default, most of these methods involve implementing them to allow for nesting.
63
+
64
+ ## Contributing
65
+
66
+ 1. Fork it
67
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
68
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
69
+ 4. Push to the branch (`git push origin my-new-feature`)
70
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
@@ -0,0 +1,3 @@
1
+ module RedisStorageMethods
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,228 @@
1
+ #This module is for Toplevel Objects that are intended for storage in redis, with possible nested subobjects that are not meant to be
2
+ #stored independently in redis.
3
+
4
+ # So a Battle is a Toplevel Object. Video is not - it's always nested in redis, so it's just normal. Video does not include this
5
+ #module, but Battle would. A Toplevel Object should not reference another Toplevel Object via an association, but if it must, do not
6
+ #put it in the associations_names method so that this Toplevel object doesnt try to store it. That's what this module uses
7
+ # to determine associations with.
8
+ module RedisStorageMethods
9
+
10
+ module ClassMethods
11
+ def redis
12
+ unless @redis
13
+ @redis ||= Redis.new( :driver => :hiredis,
14
+ :host => ENV['redis_host'],
15
+ :port => ENV['redis_port'])
16
+ @redis.auth(ENV['redis_auth']) if ENV['redis_auth']
17
+ end
18
+
19
+ @tries = 0 unless @tries
20
+
21
+ begin
22
+ @redis.get("test") #tests to see if we're still authed.
23
+ @tries = 0 #means we can reset our trymeter.
24
+ rescue Exception => e
25
+ @tries+=1 if @tries
26
+ if @tries<5
27
+ @redis = nil
28
+ redis #reset it up.
29
+ end
30
+ end
31
+
32
+ return @redis
33
+ end
34
+
35
+ #if an object is passed as params, we know we're only putting this in redis, it already exists in db
36
+ #if params is a hash, know we're creating in both.
37
+ def create_with_associations_and_redis(params=nil)
38
+ params = sanitize_all(params)
39
+ if params.is_a?(Hash)
40
+ if params.keys.select{ |k| k if k.to_s.match(/_attributes$/) }.size>0
41
+ associations = remove_and_return_associations_hash!(params)
42
+ me = self.create(params)
43
+ raise "Invalid #{me.class}: #{me.errors[me.errors.keys.first].first}" unless me.valid?
44
+ me.create_associations_from_params(associations)
45
+ else
46
+ me = self.create(params) # if submits as normal form, not AR form.
47
+ raise "Invalid #{me.class}: #{me.errors[me.errors.keys.first].first}" unless me.valid?
48
+ end
49
+ else
50
+ me = params
51
+ end
52
+
53
+ redis.pipelined {
54
+ redis.del("#{me.class.to_s.downcase}:#{me.id}") #overwrite it, make sure.
55
+ redis.hmset("#{me.class.to_s.downcase}:#{me.id}", me.assemble_key_value_array_for_redis)
56
+ redis.sadd("#{me.class.to_s.downcase.pluralize}", me.id)
57
+ me.create_custom_redis_fields
58
+ }
59
+
60
+ me.after_redis_create_do
61
+ return me
62
+ end
63
+
64
+ def sanitize_all(params)
65
+ #below sanitizes all top level inputs
66
+ return nil unless params
67
+ to_ret = nil
68
+ if params.is_a?(Hash)
69
+ to_ret = params.inject(Hash.new){ |hash, entry| hash[entry[0]] = entry[1].is_a?(String) ? Sanitize.clean(entry[1]) : sanitize_all(entry[1]); hash}
70
+ elsif params.is_a?(Array)
71
+ to_ret = params.inject(Array.new){ |arr, entry| arr << (entry.is_a?(String) ? Sanitize.clean(entry) : sanitize_all(entry)); arr}
72
+ end
73
+ return to_ret
74
+ end
75
+
76
+ #form submits with something like { "fields" => keys... "video_attributes" => { "0" => { "fields" => keys}}}
77
+ #but DataMapper doesnt support nested form submission like active record, so we have to remove these
78
+ #nestings and then handle them appropriately here..We use a class var that the class must set to know what
79
+ #to pull out.
80
+ def remove_and_return_associations_hash!(params)
81
+ associations = Hash.new
82
+ associations_names.each do |a|
83
+ associations[a] = params.delete("#{a.singularize}_attributes")
84
+ end
85
+ associations
86
+ end
87
+
88
+ #returns all redis stored objects
89
+ def all_redis
90
+ selfs = []
91
+ redis.smembers(self.to_s.downcase.pluralize).each do |id|
92
+ selfs << self.find_with_redis(id)
93
+ end
94
+ selfs
95
+ end
96
+
97
+ #populates a model with redis hash
98
+ #note you cannot write with this guy, it won't let you
99
+ #even though all the fields are set.
100
+ def find_with_redis(id)
101
+ me = self.new
102
+ redis_attr = redis.hgetall("#{me.class.to_s.downcase}:#{id}")
103
+ return nil unless redis_attr["id"]
104
+ me.attributes = redis_attr.reject { |field| field.match(/^\w+\d+\w+$/) or field.match(/^\w+_.*_\w+$/)}
105
+ me.populate_associations_from_redis(redis_attr.select { |field| field.match(/\w+\d\w+/)})
106
+ me.populate_custom_fields_from_extraneous_redis_calls
107
+ me
108
+ end
109
+
110
+ #should never get called by client, is method for cronjob to update db.
111
+ def sync_all_with_redis
112
+ self.all.each do |o|
113
+ o.sync_with_redis
114
+ end
115
+ end
116
+
117
+ #emergency method if redis db was lost, repopulate associations and stuff.
118
+ def put_all_in_redis
119
+ self.all.each do |a|
120
+ a.put_in_redis
121
+ end
122
+ end
123
+
124
+ #implementable
125
+
126
+ # ["videos", "user", ...] plural if an array, singular if a has_one/belongs_to.
127
+ def associations_names
128
+ raise 'Must be implemented in object class'
129
+ end
130
+
131
+ end
132
+
133
+ def self.included(base)
134
+ base.extend ClassMethods
135
+ end
136
+
137
+ #instance methods
138
+
139
+ #must also define redis down here so it gets set,
140
+ #there is an instance on the class object and instances of it then.
141
+ def redis
142
+ @redis ||= self.class.redis
143
+ end
144
+
145
+ def assemble_key_value_array_for_redis
146
+ a = Array.new
147
+ self.class.properties.map { |prop| prop.name.to_s }.each do |p|
148
+ if(self.send(p))
149
+ #this if statement is so nil values wont be stored as "" in redis
150
+ #and come back as an empty string, they will come back as nil.
151
+ a<<p
152
+ a<<self.send(p)
153
+ end
154
+ end
155
+ self.add_key_value_pairs_of_associations_for_redis!(a)
156
+ a
157
+ end
158
+
159
+ def destroy_with_redis
160
+ self.destroy
161
+ redis.del("#{self.class.to_s.downcase}:#{self.id}")
162
+ end
163
+
164
+ #this is a helper method that will construct objects from redis that have no
165
+ #nested associations themselves. So we can keep code DRY. If you have nesting,
166
+ #you must do it yourself.
167
+ def construct_low_level_model_from_redis_hash(redis_hash, association)
168
+ found = true
169
+ i = 0
170
+ while(found) do
171
+ if redis_hash["#{association.singularize}#{i}id"]
172
+ params_hash = {}
173
+
174
+ redis_hash.select {|k, v| k.to_s.match(/#{association.singularize}#{i}/)}.each do |key, value|
175
+ params_hash[key.to_s.match(/#{association.singularize}#{i}(\w+)/)[1]] = value
176
+ end
177
+
178
+ self.send(association)<< Kernel.const_get(association.singularize.capitalize).new(params_hash)
179
+ else
180
+ found = false and break
181
+ end
182
+ i+=1
183
+ end
184
+ end
185
+
186
+ #if the db object exists but needs to be placed in redis
187
+ def put_in_redis
188
+ self.class.create_with_associations_and_redis(self)
189
+ end
190
+
191
+ #implementable
192
+
193
+ #accepts the part of the form hash with nested atributes(but with attributes taken off keys), like { "videos" => {"0" => {field => value}}}
194
+ #and then creates models. Because we don't support nested creation at the meta level, must be handled locally.
195
+ def create_associations_from_params(params)
196
+ raise 'Must be implemented by the object class!'
197
+ end
198
+
199
+ #No nesting in Redis, no methods for listing associations on DataMapper, This is custom, returns nested objects for use in storage
200
+ #with redis. Because I dont want to write metacode that detects nested objects within my own nested objects, I'm just leaving it
201
+ # to each model to implement this in a custom fashion.
202
+ def add_key_value_pairs_of_associations_for_redis!(array)
203
+ raise 'Must be implemented by object class!'
204
+ end
205
+
206
+ #expects to get hash of things like video0video_id => "555" from redis, needs to create associations from it.
207
+ def populate_associations_from_redis(redis_hash)
208
+ raise 'Must be implemented by object class!'
209
+ end
210
+
211
+ #if you have sets stored outside the redis hash, need to make a call to get them, haven't you?
212
+ # you can do that here, or leave this a nil method. It won't hurt you.
213
+ def populate_custom_fields_from_extraneous_redis_calls
214
+ end
215
+
216
+ #method to sync object in db with redis.
217
+ def sync_with_redis
218
+ raise 'Must be implemented by object class!'
219
+ end
220
+
221
+ #create the custom fields you need like lists for arrays etc, artist:name => id references,
222
+ #stuff like that.
223
+ def create_custom_redis_fields
224
+ end
225
+
226
+ def after_redis_create_do
227
+ end
228
+ end
@@ -0,0 +1,17 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/redis_storage_methods/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Jordan Prince"]
6
+ gem.email = ["jordanmprince@gmail.com"]
7
+ gem.description = %q{Transparently store objects both in redis and your sql store, but GET only on the redis store for speed.}
8
+ gem.summary = %q{Transparently store objects both in redis and your sql store, but GET only on the redis store for speed.}
9
+ gem.homepage = ""
10
+
11
+ gem.files = `git ls-files`.split($\)
12
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
13
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
14
+ gem.name = "redis_storage_methods"
15
+ gem.require_paths = ["lib"]
16
+ gem.version = RedisStorageMethods::VERSION
17
+ end
metadata ADDED
@@ -0,0 +1,55 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: redis_storage_methods
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Jordan Prince
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-02-05 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description: Transparently store objects both in redis and your sql store, but GET
15
+ only on the redis store for speed.
16
+ email:
17
+ - jordanmprince@gmail.com
18
+ executables: []
19
+ extensions: []
20
+ extra_rdoc_files: []
21
+ files:
22
+ - .gitignore
23
+ - Gemfile
24
+ - LICENSE
25
+ - README.md
26
+ - Rakefile
27
+ - lib/redis_storage_methods.rb
28
+ - lib/redis_storage_methods/version.rb
29
+ - redis_storage_methods.gemspec
30
+ homepage: ''
31
+ licenses: []
32
+ post_install_message:
33
+ rdoc_options: []
34
+ require_paths:
35
+ - lib
36
+ required_ruby_version: !ruby/object:Gem::Requirement
37
+ none: false
38
+ requirements:
39
+ - - ! '>='
40
+ - !ruby/object:Gem::Version
41
+ version: '0'
42
+ required_rubygems_version: !ruby/object:Gem::Requirement
43
+ none: false
44
+ requirements:
45
+ - - ! '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ requirements: []
49
+ rubyforge_project:
50
+ rubygems_version: 1.8.11
51
+ signing_key:
52
+ specification_version: 3
53
+ summary: Transparently store objects both in redis and your sql store, but GET only
54
+ on the redis store for speed.
55
+ test_files: []