redisted 0.0.1
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/.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
|