redisted 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +7 -0
- data/Gemfile +16 -0
- data/Guardfile +31 -0
- data/README.md +643 -0
- data/Rakefile +1 -0
- data/config/application.rb +59 -0
- data/config/boot.rb +6 -0
- data/config/environment.rb +7 -0
- data/config/redis.rb +17 -0
- data/config/redis.yml +0 -0
- data/index.html +1 -0
- data/lib/redisted/base.rb +129 -0
- data/lib/redisted/delete.rb +21 -0
- data/lib/redisted/errors.rb +12 -0
- data/lib/redisted/fields.rb +59 -0
- data/lib/redisted/getsetattr.rb +220 -0
- data/lib/redisted/index.rb +330 -0
- data/lib/redisted/instantiate.rb +34 -0
- data/lib/redisted/references.rb +105 -0
- data/lib/redisted/relation.rb +190 -0
- data/lib/redisted/version.rb +3 -0
- data/lib/redisted.rb +10 -0
- data/log/development.log +0 -0
- data/log/test.log +0 -0
- data/readme.md +643 -0
- data/redisted.gemspec +23 -0
- data/spec/redisted/callbacks_spec.rb +76 -0
- data/spec/redisted/delete_destroy_spec.rb +49 -0
- data/spec/redisted/fields_spec.rb +118 -0
- data/spec/redisted/filter_index_spec.rb +90 -0
- data/spec/redisted/find_spec.rb +30 -0
- data/spec/redisted/log/test.log +0 -0
- data/spec/redisted/match_index_spec.rb +36 -0
- data/spec/redisted/persisted_fields_spec.rb +238 -0
- data/spec/redisted/recalculate_index_spec.rb +6 -0
- data/spec/redisted/references_spec.rb +128 -0
- data/spec/redisted/scopes_spec.rb +88 -0
- data/spec/redisted/unique_index_spec.rb +113 -0
- data/spec/redisted/validations_spec.rb +71 -0
- data/spec/spec_helper.rb +61 -0
- metadata +110 -0
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require File.expand_path('../boot', __FILE__)
|
2
|
+
|
3
|
+
# Pick the frameworks you want:
|
4
|
+
# require "active_record/railtie"
|
5
|
+
require "action_controller/railtie"
|
6
|
+
require "action_mailer/railtie"
|
7
|
+
require "active_resource/railtie"
|
8
|
+
require "sprockets/railtie"
|
9
|
+
require "rails/test_unit/railtie"
|
10
|
+
require File.expand_path('../redis', __FILE__)
|
11
|
+
|
12
|
+
if defined?(Bundler)
|
13
|
+
# If you precompile assets before deploying to production, use this line
|
14
|
+
Bundler.require *Rails.groups(:assets => %w(development test))
|
15
|
+
# If you want your assets lazily compiled in production, use this line
|
16
|
+
# Bundler.require(:default, :assets, Rails.env)
|
17
|
+
end
|
18
|
+
|
19
|
+
module Redisted
|
20
|
+
class Application < Rails::Application
|
21
|
+
# Settings in config/environments/* take precedence over those specified here.
|
22
|
+
# Application configuration should go into files in config/initializers
|
23
|
+
# -- all .rb files in that directory are automatically loaded.
|
24
|
+
|
25
|
+
# Custom directories with classes and modules you want to be autoloadable.
|
26
|
+
# config.autoload_paths += %W(#{config.root}/extras)
|
27
|
+
|
28
|
+
# Only load the plugins named here, in the order given (default is alphabetical).
|
29
|
+
# :all can be used as a placeholder for all plugins not explicitly named.
|
30
|
+
# config.plugins = [ :exception_notification, :ssl_requirement, :all ]
|
31
|
+
|
32
|
+
# Activate observers that should always be running.
|
33
|
+
# config.active_record.observers = :cacher, :garbage_collector, :forum_observer
|
34
|
+
|
35
|
+
# Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
|
36
|
+
# Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
|
37
|
+
config.time_zone = 'UTC'
|
38
|
+
|
39
|
+
# The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
|
40
|
+
# config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
|
41
|
+
# config.i18n.default_locale = :de
|
42
|
+
|
43
|
+
# Configure the default encoding used in templates for Ruby 1.9.
|
44
|
+
config.encoding = "utf-8"
|
45
|
+
|
46
|
+
# Configure sensitive parameters which will be filtered from the log file.
|
47
|
+
config.filter_parameters += [:password]
|
48
|
+
|
49
|
+
# Enable the asset pipeline
|
50
|
+
config.assets.enabled = true
|
51
|
+
|
52
|
+
# Version of your assets, change this if you want to expire all your assets
|
53
|
+
config.assets.version = '1.0'
|
54
|
+
|
55
|
+
|
56
|
+
config.active_support.deprecation = :stderr
|
57
|
+
|
58
|
+
end
|
59
|
+
end
|
data/config/boot.rb
ADDED
data/config/redis.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
#
|
2
|
+
# Setup Redis
|
3
|
+
#
|
4
|
+
require 'redis'
|
5
|
+
require 'redisted'
|
6
|
+
begin
|
7
|
+
$redis=Redis.new({
|
8
|
+
host: "localhost",
|
9
|
+
port: 6379,
|
10
|
+
timeout: 5,
|
11
|
+
})
|
12
|
+
$redis.select 15 # Database #15
|
13
|
+
Redisted::Base.redis=$redis
|
14
|
+
rescue =>err
|
15
|
+
puts "Redisted Test redis error: #{err}"
|
16
|
+
raise "Redisted Test redis error: #{err}"
|
17
|
+
end
|
data/config/redis.yml
ADDED
File without changes
|
data/index.html
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
Test Page
|
@@ -0,0 +1,129 @@
|
|
1
|
+
#
|
2
|
+
#
|
3
|
+
# Creates the following Redis keys for a class named "Model"
|
4
|
+
#
|
5
|
+
# model:<id> -- HASH -- contains all attributes of the class
|
6
|
+
# model_id -- Last ID used (integer used with INCR command)
|
7
|
+
#
|
8
|
+
#
|
9
|
+
module Redisted
|
10
|
+
class Base
|
11
|
+
include ActiveModel::MassAssignmentSecurity
|
12
|
+
include ActiveModel::Validations
|
13
|
+
include ActiveModel::Serialization
|
14
|
+
#include ActiveModel::Dirty -- This model doesn't match very well...
|
15
|
+
extend ActiveModel::Callbacks
|
16
|
+
extend ActiveModel::Naming
|
17
|
+
extend ActiveModel::Translation
|
18
|
+
|
19
|
+
define_model_callbacks :create, :update, :save, :destroy
|
20
|
+
|
21
|
+
def initialize(attrs = nil,redis=nil)
|
22
|
+
@redis=redis||Base.redis
|
23
|
+
validate_redis
|
24
|
+
init_fields
|
25
|
+
init_attributes
|
26
|
+
init_indexes
|
27
|
+
init_references
|
28
|
+
self.attributes=attrs if attrs
|
29
|
+
self
|
30
|
+
end
|
31
|
+
def redis= conn=nil
|
32
|
+
@redis=conn||@@redis
|
33
|
+
validate_redis
|
34
|
+
@redis
|
35
|
+
end
|
36
|
+
def redis
|
37
|
+
validate_redis
|
38
|
+
@redis
|
39
|
+
end
|
40
|
+
def prefix
|
41
|
+
self.class.prefix
|
42
|
+
end
|
43
|
+
def id # NOTE: ID is read/only - it can only be set via the load method
|
44
|
+
@id
|
45
|
+
end
|
46
|
+
def to_attr_type key,value
|
47
|
+
raise InvalidField,"Unknown field: #{key}" if fields[key].nil?
|
48
|
+
return nil if value.nil?
|
49
|
+
case fields[key][:type]
|
50
|
+
when :string then value
|
51
|
+
when :symbol then value.to_sym
|
52
|
+
when :integer then value.to_i
|
53
|
+
when :datetime then value.to_datetime
|
54
|
+
else
|
55
|
+
value
|
56
|
+
end
|
57
|
+
end
|
58
|
+
def to_redis_type key,value
|
59
|
+
return nil if value.nil?
|
60
|
+
case fields[key][:type]
|
61
|
+
when :string then value
|
62
|
+
when :symbol then value.to_s
|
63
|
+
when :integer then value.to_i.to_s
|
64
|
+
when :datetime then value.utc.to_s
|
65
|
+
else
|
66
|
+
value
|
67
|
+
end
|
68
|
+
end
|
69
|
+
def method_missing(id, *args)
|
70
|
+
fields.each do |field,options|
|
71
|
+
return get_attr(field) if id==field
|
72
|
+
return set_attr(field,args[0]) if id.to_s==field.to_s+"="
|
73
|
+
end
|
74
|
+
references.each do |ref,options|
|
75
|
+
return get_reference(ref,options) if id==ref
|
76
|
+
return set_reference(ref,options,args[0]) if id.to_s==ref.to_s+"="
|
77
|
+
end
|
78
|
+
super
|
79
|
+
end
|
80
|
+
|
81
|
+
private
|
82
|
+
def validate_redis
|
83
|
+
raise RedisConnectionNotDefined if @redis.nil?
|
84
|
+
end
|
85
|
+
|
86
|
+
def generate_id
|
87
|
+
validate_redis
|
88
|
+
@@redis.incr "#{prefix}_id"
|
89
|
+
end
|
90
|
+
|
91
|
+
|
92
|
+
def get_obj_option key
|
93
|
+
self.class.get_obj_option key
|
94
|
+
end
|
95
|
+
def set_obj_option key,val
|
96
|
+
self.class.set_obj_option key,val
|
97
|
+
end
|
98
|
+
class << self
|
99
|
+
def get_obj_option key
|
100
|
+
@obj_options||={}
|
101
|
+
@obj_options[key]
|
102
|
+
end
|
103
|
+
def set_obj_option key,val
|
104
|
+
@obj_options||={}
|
105
|
+
@obj_options[key]=val
|
106
|
+
end
|
107
|
+
def is_redisted_model?
|
108
|
+
true
|
109
|
+
end
|
110
|
+
def redis= conn
|
111
|
+
@@redis=conn
|
112
|
+
end
|
113
|
+
def redis
|
114
|
+
@@redis
|
115
|
+
end
|
116
|
+
def prefix
|
117
|
+
ret=self.model_name.i18n_key
|
118
|
+
ret
|
119
|
+
end
|
120
|
+
|
121
|
+
private
|
122
|
+
|
123
|
+
def validate_redis
|
124
|
+
raise RedisConnectionNotDefined if @@redis.nil?
|
125
|
+
end
|
126
|
+
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Redisted
|
2
|
+
class Base
|
3
|
+
|
4
|
+
def delete
|
5
|
+
raise Redisted::RecordInvalid,"Object is not persisted" if self.id.nil?
|
6
|
+
@redis.del "#{prefix}:#{self.id}"
|
7
|
+
id=nil
|
8
|
+
attributes={}
|
9
|
+
@attributes_status.clear
|
10
|
+
# LEELEE: Also handle indices
|
11
|
+
true
|
12
|
+
end
|
13
|
+
def destroy
|
14
|
+
# TODO: Run all the callbacks...
|
15
|
+
delete
|
16
|
+
# LEELEE: Follow destroy path...
|
17
|
+
true
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module Redisted
|
2
|
+
class RedisConnectionNotDefined<Exception;end # No redis connection was found
|
3
|
+
class RedisSaveFailed<Exception;end
|
4
|
+
class RecordInvalid<Exception;end
|
5
|
+
class InvalidQuery<Exception;end
|
6
|
+
class InvalidReference<Exception;end
|
7
|
+
class ValueNotUnique<Exception;end
|
8
|
+
class MultiSessionConflict<Exception;end
|
9
|
+
class IsDirty<Exception;end
|
10
|
+
class InvalidIndex<Exception;end
|
11
|
+
class InvalidField<Exception;end
|
12
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module Redisted
|
2
|
+
class Base
|
3
|
+
def fields
|
4
|
+
self.class.fields
|
5
|
+
end
|
6
|
+
def scopes
|
7
|
+
self.class.scopes
|
8
|
+
end
|
9
|
+
private
|
10
|
+
def init_fields
|
11
|
+
end
|
12
|
+
class << self
|
13
|
+
public
|
14
|
+
#
|
15
|
+
# Setup
|
16
|
+
#
|
17
|
+
def field value,options={}
|
18
|
+
options[:type]||=:string
|
19
|
+
fields[value]=options
|
20
|
+
end
|
21
|
+
def fields
|
22
|
+
@fields||={}
|
23
|
+
@fields
|
24
|
+
end
|
25
|
+
def fields= val
|
26
|
+
@fields=val
|
27
|
+
end
|
28
|
+
def scopes
|
29
|
+
@scopes||={}
|
30
|
+
@scopes
|
31
|
+
end
|
32
|
+
def scopes= val
|
33
|
+
@scopes=val
|
34
|
+
end
|
35
|
+
def scope name,options
|
36
|
+
scopes[name]=lambdafi_field(options)
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def read_attribute_for_validation key
|
42
|
+
get_attr key
|
43
|
+
end
|
44
|
+
|
45
|
+
def lambdafi_field options
|
46
|
+
lambda do |*args|
|
47
|
+
if options.respond_to? :call
|
48
|
+
options.call(*args)
|
49
|
+
elsif options.is_a? Symbol
|
50
|
+
args[0].send(options,*args)
|
51
|
+
else
|
52
|
+
options # An integer or other constant value
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,220 @@
|
|
1
|
+
module Redisted
|
2
|
+
class Base
|
3
|
+
class << self
|
4
|
+
def always_cache_until_save val=true
|
5
|
+
set_obj_option :cache_until_save,val
|
6
|
+
end
|
7
|
+
def pre_cache_all opts={}
|
8
|
+
opts[:keys]=:all if opts[:keys].nil?
|
9
|
+
opts[:when]=:create if opts[:when].nil?
|
10
|
+
set_obj_option :pre_cache_all,opts
|
11
|
+
end
|
12
|
+
end
|
13
|
+
private
|
14
|
+
def init_attributes
|
15
|
+
@attributes_value={}
|
16
|
+
@attributes_status={}
|
17
|
+
@attributes_old={}
|
18
|
+
@attributes_old_status={}
|
19
|
+
@cache_preload=false
|
20
|
+
@cached_sets=false
|
21
|
+
@cached_sets=true if get_obj_option :cache_until_save
|
22
|
+
end
|
23
|
+
def pre_cache_values
|
24
|
+
return if @cache_preload
|
25
|
+
@cache_preload=true # Must be set first to prevent get_attr loop...
|
26
|
+
keys=get_obj_option(:pre_cache_all)[:keys]
|
27
|
+
except=get_obj_option(:pre_cache_all)[:except]
|
28
|
+
if keys==:all
|
29
|
+
fields.each do |k,v|
|
30
|
+
next if except and except.include? k
|
31
|
+
get_attr k
|
32
|
+
end
|
33
|
+
else
|
34
|
+
keys.each do |k|
|
35
|
+
next if except and except.include? k
|
36
|
+
get_attr k
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
public
|
41
|
+
def set_attr key,val
|
42
|
+
key=key.to_sym
|
43
|
+
val=to_redis_type key,val
|
44
|
+
if @cached_sets or !persisted?
|
45
|
+
if (@attributes_status[key]==:cached)
|
46
|
+
# If we have it, we save the old value when first marked dirty...
|
47
|
+
@attributes_old[key]=@attributes_value[key]
|
48
|
+
@attributes_old_status[key]=:cached
|
49
|
+
end
|
50
|
+
@attributes_value[key]=val
|
51
|
+
@attributes_status[key]=:dirty
|
52
|
+
else
|
53
|
+
index_process({key=>val}) do
|
54
|
+
@attributes_value[key]=val
|
55
|
+
@attributes_status[key]=:cached
|
56
|
+
@redis.hset "#{prefix}:#{@id}",key,val
|
57
|
+
@attributes_old[key]=nil
|
58
|
+
@attributes_old_status[key]=nil
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
def get_attr key
|
63
|
+
key=key.to_sym
|
64
|
+
return to_attr_type(key,@attributes_value[key]) if [:cached,:dirty].include?(@attributes_status[key])
|
65
|
+
return nil if !persisted?
|
66
|
+
ret=to_attr_type key,@redis.hget("#{prefix}:#{@id}",key)
|
67
|
+
pre_cache_values if get_obj_option(:pre_cache_all) and get_obj_option(:pre_cache_all)[:when]==:first_read and !@cache_preload
|
68
|
+
@attributes_value[key]=ret
|
69
|
+
@attributes_status[key]=:cached
|
70
|
+
ret
|
71
|
+
end
|
72
|
+
|
73
|
+
def clear key=nil
|
74
|
+
if key.nil?
|
75
|
+
self.class.fields.each do |key,val|
|
76
|
+
raise IsDirty,"#{key} is dirty" if (!key.nil?) and @attributes_status[key]==:dirty
|
77
|
+
end
|
78
|
+
end
|
79
|
+
setup_attributes
|
80
|
+
nil
|
81
|
+
end
|
82
|
+
def flush
|
83
|
+
fields.each do |key,val|
|
84
|
+
@attributes_status[key]=nil
|
85
|
+
end
|
86
|
+
end
|
87
|
+
def fill_cache keys=nil
|
88
|
+
if keys.nil?
|
89
|
+
keys=[]
|
90
|
+
fields.each do |key,val|
|
91
|
+
next if !@attributes_status[key].nil?
|
92
|
+
keys << key
|
93
|
+
end
|
94
|
+
end
|
95
|
+
if keys.size>0
|
96
|
+
ret=@redis.hmget "#{prefix}:#{@id}",*keys
|
97
|
+
idx=0
|
98
|
+
keys.each do |key|
|
99
|
+
key=key.to_sym
|
100
|
+
@attributes_value[key]=ret[idx]
|
101
|
+
@attributes_status[key]=:cached
|
102
|
+
idx+=1
|
103
|
+
end
|
104
|
+
end
|
105
|
+
self
|
106
|
+
end
|
107
|
+
def persisted?
|
108
|
+
!@id.nil?
|
109
|
+
end
|
110
|
+
def cached? key
|
111
|
+
return true if !persisted?
|
112
|
+
return [:cached,:dirty].include?(@attributes_status[key])
|
113
|
+
end
|
114
|
+
def dirty? key
|
115
|
+
return true if !persisted?
|
116
|
+
return @attributes_status[key]==:dirty
|
117
|
+
end
|
118
|
+
def saved?
|
119
|
+
return false if !persisted?
|
120
|
+
fields.each do |key,val|
|
121
|
+
return false if @attributes_status[key]==:dirty
|
122
|
+
end
|
123
|
+
return true
|
124
|
+
end
|
125
|
+
def cache
|
126
|
+
if block_given?
|
127
|
+
previous_cached_sets=nil
|
128
|
+
begin
|
129
|
+
previous_cached_sets=@cached_sets
|
130
|
+
@cached_sets=true
|
131
|
+
yield
|
132
|
+
save if !previous_cached_sets
|
133
|
+
@cached_sets=previous_cached_sets
|
134
|
+
rescue Exception
|
135
|
+
@cached_sets=previous_cached_sets
|
136
|
+
raise
|
137
|
+
end
|
138
|
+
else
|
139
|
+
@cached_sets=true
|
140
|
+
end
|
141
|
+
end
|
142
|
+
def cache! &proc
|
143
|
+
begin
|
144
|
+
@cached_sets=true
|
145
|
+
proc.call *attrs
|
146
|
+
save!
|
147
|
+
rescue Exception
|
148
|
+
@cached_sets=false
|
149
|
+
raise
|
150
|
+
end
|
151
|
+
end
|
152
|
+
def attributes= attrs
|
153
|
+
cache do
|
154
|
+
attrs.each do |key,value|
|
155
|
+
set_attr key,value
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
def attributes load_all=true
|
160
|
+
fill_cache if load_all
|
161
|
+
@attributes_value.merge id: @id
|
162
|
+
end
|
163
|
+
|
164
|
+
def save!
|
165
|
+
raise Redisted::RecordInvalid if !valid?
|
166
|
+
internal_save_with_callbacks
|
167
|
+
end
|
168
|
+
|
169
|
+
def save options={}
|
170
|
+
begin
|
171
|
+
if (options[:validate].nil? or options[:validate])
|
172
|
+
raise Redisted::RecordInvalid if !valid?
|
173
|
+
end
|
174
|
+
internal_save_with_callbacks
|
175
|
+
rescue Exception=>err
|
176
|
+
self.errors[:base] = err.to_s
|
177
|
+
return false
|
178
|
+
end
|
179
|
+
true
|
180
|
+
end
|
181
|
+
|
182
|
+
private
|
183
|
+
def internal_save_with_callbacks
|
184
|
+
cb_run=run_callbacks :save do
|
185
|
+
if persisted?
|
186
|
+
run_callbacks :update do
|
187
|
+
internal_save false
|
188
|
+
end
|
189
|
+
else
|
190
|
+
run_callbacks :create do
|
191
|
+
internal_save true
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
raise Redisted::RecordInvalid,"Callback link broken" if !cb_run
|
196
|
+
end
|
197
|
+
def internal_save is_create
|
198
|
+
@id=generate_id if is_create
|
199
|
+
params={}
|
200
|
+
params[:id]=@id if is_create
|
201
|
+
@attributes_status.each do |key,value|
|
202
|
+
next if value!=:dirty
|
203
|
+
params[key]=@attributes_value[key]
|
204
|
+
end
|
205
|
+
if params.size>0
|
206
|
+
index_process(params) do
|
207
|
+
@redis.hmset("#{prefix}:#{@id}",*(params.flatten))
|
208
|
+
@attributes_status.each do |key,value|
|
209
|
+
next if value!=:dirty
|
210
|
+
@attributes_status[key]=:cached
|
211
|
+
end
|
212
|
+
@attributes_old={}
|
213
|
+
@attributes_old_status={}
|
214
|
+
end
|
215
|
+
end
|
216
|
+
@hold_writes=false
|
217
|
+
self
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|