redis_object 0.5.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/.coveralls.yml +1 -0
- data/.gitignore +6 -0
- data/.travis.yml +5 -0
- data/Gemfile +8 -0
- data/README.markdown +179 -0
- data/Rakefile +10 -0
- data/lib/redis_object.rb +47 -0
- data/lib/redis_object/base.rb +408 -0
- data/lib/redis_object/collection.rb +388 -0
- data/lib/redis_object/defaults.rb +42 -0
- data/lib/redis_object/experimental/history.rb +49 -0
- data/lib/redis_object/ext/benchmark.rb +34 -0
- data/lib/redis_object/ext/cleaner.rb +14 -0
- data/lib/redis_object/ext/filters.rb +68 -0
- data/lib/redis_object/ext/script_cache.rb +92 -0
- data/lib/redis_object/ext/shardable.rb +18 -0
- data/lib/redis_object/ext/triggers.rb +101 -0
- data/lib/redis_object/ext/view_caching.rb +258 -0
- data/lib/redis_object/ext/views.rb +102 -0
- data/lib/redis_object/external_index.rb +25 -0
- data/lib/redis_object/indices.rb +97 -0
- data/lib/redis_object/inheritance_tracking.rb +23 -0
- data/lib/redis_object/keys.rb +37 -0
- data/lib/redis_object/storage.rb +93 -0
- data/lib/redis_object/storage/adapter.rb +46 -0
- data/lib/redis_object/storage/aws.rb +71 -0
- data/lib/redis_object/storage/mysql.rb +47 -0
- data/lib/redis_object/storage/redis.rb +119 -0
- data/lib/redis_object/timestamps.rb +74 -0
- data/lib/redis_object/tpl.rb +17 -0
- data/lib/redis_object/types.rb +276 -0
- data/lib/redis_object/validation.rb +89 -0
- data/lib/redis_object/version.rb +5 -0
- data/redis_object.gemspec +26 -0
- data/spec/adapter_spec.rb +43 -0
- data/spec/base_spec.rb +90 -0
- data/spec/benchmark_spec.rb +46 -0
- data/spec/collections_spec.rb +144 -0
- data/spec/defaults_spec.rb +56 -0
- data/spec/filters_spec.rb +29 -0
- data/spec/indices_spec.rb +45 -0
- data/spec/rename_class_spec.rb +96 -0
- data/spec/spec_helper.rb +38 -0
- data/spec/timestamp_spec.rb +28 -0
- data/spec/trigger_spec.rb +51 -0
- data/spec/types_spec.rb +103 -0
- data/spec/view_caching_spec.rb +130 -0
- data/spec/views_spec.rb +72 -0
- metadata +172 -0
data/.coveralls.yml
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
repo_token: 8vq1j1WGB56M8h1zI6TeuK7x3HVJ1K8l4
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/README.markdown
ADDED
@@ -0,0 +1,179 @@
|
|
1
|
+
# RedisObject
|
2
|
+
RedisObject is a fast and simple-to-use object persistence layer for Ruby.
|
3
|
+
|
4
|
+
[](https://travis-ci.org/remotezygote/RedisObject)
|
5
|
+
[](https://coveralls.io/r/remotezygote/RedisObject?branch=master)
|
6
|
+
|
7
|
+
## Prerequisites
|
8
|
+
You'll need [Redis](http://redis.io). Other storage adapters are in the works. Maybe.
|
9
|
+
|
10
|
+
|
11
|
+
## Installation
|
12
|
+
|
13
|
+
gem install redis_object
|
14
|
+
|
15
|
+
Or, you can add it to your Gemfile:
|
16
|
+
|
17
|
+
gem 'redis_object'
|
18
|
+
|
19
|
+
|
20
|
+
## Usage
|
21
|
+
### Simple Example
|
22
|
+
```ruby
|
23
|
+
class Thing < RedisObject
|
24
|
+
def name
|
25
|
+
"#{first_name} #{last_name}"
|
26
|
+
end
|
27
|
+
def name=(new_name)
|
28
|
+
first, last = new_name.split(" ")
|
29
|
+
set(:first_name,first)
|
30
|
+
set(:last_name,last)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
a = Thing.create("an_id")
|
34
|
+
a.name = "Testy Testerton"
|
35
|
+
b = Thing.create({:first_name => "Testy", :last_name => "Testerton"})
|
36
|
+
```
|
37
|
+
|
38
|
+
### Config
|
39
|
+
You can configure the storage adapter by sending a packet of commands to `configure_store` like:
|
40
|
+
|
41
|
+
```ruby
|
42
|
+
RedisObject.configure_store({:db: 2})
|
43
|
+
```
|
44
|
+
|
45
|
+
The default storage adapter is `Redis`. The above config will connect to Redis on localhost on the default port (6379), but will `select` database number 2.
|
46
|
+
|
47
|
+
Or, you can configure multiple stores to use within an app by passing a second parameter to name the store (default is 'general')
|
48
|
+
|
49
|
+
```ruby
|
50
|
+
RedisObject.configure_store({adapter: "Redis", :db: 4, :path: "/var/run/redis.sock"}, :message_queue)
|
51
|
+
|
52
|
+
class Message < RedisObject
|
53
|
+
use_store :message_queue
|
54
|
+
end
|
55
|
+
```
|
56
|
+
|
57
|
+
## 'Collections'
|
58
|
+
Object relationships are stored in collections of objects attached to other objects. To 'collect' an object onto another, you simply call `reference` to reference the objects (also aliased to the concat operator `<<`).
|
59
|
+
|
60
|
+
Collections are automatically created, and can be access by their plural, lower-case name to gather all of the items in a collection (returns an Enumerable `Collection` object), or by its singular lower-case name to just get one somewhat randomly (useful for 1 -> 1 style relationships).
|
61
|
+
|
62
|
+
Example:
|
63
|
+
|
64
|
+
```ruby
|
65
|
+
class Person < RedisObject; end
|
66
|
+
class Address < RedisObject; end
|
67
|
+
john = Person.create("john")
|
68
|
+
john << Address.create({
|
69
|
+
:street => "123 Main St.",
|
70
|
+
:city => "San Francisco",
|
71
|
+
:state => "CA",
|
72
|
+
:zip => "12345"
|
73
|
+
})
|
74
|
+
|
75
|
+
john.addresses
|
76
|
+
# ["Address:john"]
|
77
|
+
john.address
|
78
|
+
# {
|
79
|
+
# :address_id => "john",
|
80
|
+
# :street => "123 Main St.",
|
81
|
+
# :city => "San Francisco",
|
82
|
+
# :state => "CA",
|
83
|
+
# :zip => "12345",
|
84
|
+
# :class=>"Address",
|
85
|
+
# :key=>"Address:john",
|
86
|
+
# :created_at=>Wed, 12 Dec 2012 16:49:26 -0800,
|
87
|
+
# :updated_at=>Wed, 12 Dec 2012 16:49:26 -0800
|
88
|
+
# }
|
89
|
+
```
|
90
|
+
|
91
|
+
You may notice that the type of object, its basic storage key, and some timestamps are automatically created and updated appropriately.
|
92
|
+
|
93
|
+
It is important to note that collections inherit any indices of its underlying object type. See Indices below for examples.
|
94
|
+
|
95
|
+
## Types
|
96
|
+
A few types of data can be specified for certain fields. The types supported are:
|
97
|
+
|
98
|
+
* Date
|
99
|
+
* Number
|
100
|
+
* Float
|
101
|
+
* Bool
|
102
|
+
* Array
|
103
|
+
* JSON (store any data that can be JSON-encoded - it will be automatically encoded/decoded when stored/accessed)
|
104
|
+
|
105
|
+
These types are also used for scoring when keeping field indices. If no type is specified, String is used, and no scoring is possible at this time.
|
106
|
+
|
107
|
+
Setting the type of a field is super easy:
|
108
|
+
|
109
|
+
```ruby
|
110
|
+
class Person < RedisObject
|
111
|
+
bool :verified
|
112
|
+
json :meta
|
113
|
+
end
|
114
|
+
|
115
|
+
john = Person.create("john")
|
116
|
+
john.meta = {:external_id => "123456", :number => 123}
|
117
|
+
john.verified # false
|
118
|
+
```
|
119
|
+
|
120
|
+
TODO: Add verified? and verified! -style methods automagically for boolean fields.
|
121
|
+
|
122
|
+
You can add your own custom types by defining filter methods for getting and setting a field, and can define a scoring function if you would like to index fields of your type.
|
123
|
+
|
124
|
+
Example:
|
125
|
+
|
126
|
+
```ruby
|
127
|
+
class Person < RedisObject
|
128
|
+
|
129
|
+
def format_boolean(val)
|
130
|
+
val=="true"
|
131
|
+
end
|
132
|
+
|
133
|
+
def save_boolean(val)
|
134
|
+
val ? "true" : "false"
|
135
|
+
end
|
136
|
+
|
137
|
+
def score_boolean(val)
|
138
|
+
val ? 1 : 0
|
139
|
+
end
|
140
|
+
|
141
|
+
class << self
|
142
|
+
def bool(k)
|
143
|
+
field_formats[k] = :format_boolean
|
144
|
+
save_formats[k] = :save_boolean
|
145
|
+
score_formats[k] = :score_boolean
|
146
|
+
end
|
147
|
+
alias_method :boolean, :bool
|
148
|
+
|
149
|
+
end
|
150
|
+
|
151
|
+
end
|
152
|
+
```
|
153
|
+
|
154
|
+
TODO: Make defining custom formats easier - no need to define class methods for this - could have helper function for it like `custom_format :bool, :get => :format_boolean` or similar.
|
155
|
+
|
156
|
+
## Indices
|
157
|
+
Any field that can be scored can store a sidecar index by that score. These indices can be used to index items in a collection (internally, it is a simple Redis set intersection, so it is very fast). Timestamps are indexed by default for any object, so out of the box you can do:
|
158
|
+
|
159
|
+
```ruby
|
160
|
+
Person.indexed(:created_at) # all Person objects, oldest first
|
161
|
+
Person.indexed(:created_at, 1, true) # newest Person (index_field, number of items, reverse sort?)
|
162
|
+
Person.latest # always available if timestamps are on - most recently created object of type
|
163
|
+
john.addresses.indexed(:created_at, 3, true) # john's 3 most recent addresses
|
164
|
+
Person.indexed(:updated_at, -1, true) do |person|
|
165
|
+
# iterate through Person objects in order of update times, most recent first
|
166
|
+
end
|
167
|
+
```
|
168
|
+
|
169
|
+
Accessing indexed items always returns an Enumerator, so first/last/each/count/etc. are usable anywhere and will access objects only when iterated.
|
170
|
+
|
171
|
+
## Named Views
|
172
|
+
`TODO: Add some damn View documentation.`
|
173
|
+
|
174
|
+
## Links
|
175
|
+
Redis: [http://redis.io](http://redis.io)
|
176
|
+
RedisObject Code: [https://github.com/remotezygote/RedisObject](https://github.com/remotezygote/RedisObject)
|
177
|
+
|
178
|
+
|
179
|
+
[rubygems]: http://rubygems.org/gems/redis_object
|
data/Rakefile
ADDED
data/lib/redis_object.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'active_support/inflector'
|
2
|
+
require 'active_support/core_ext/date_time/conversions'
|
3
|
+
require 'yajl'
|
4
|
+
|
5
|
+
require "redis_object/storage"
|
6
|
+
|
7
|
+
require "redis_object/ext/script_cache"
|
8
|
+
require "redis_object/base"
|
9
|
+
require "redis_object/inheritance_tracking"
|
10
|
+
require "redis_object/storage"
|
11
|
+
require "redis_object/keys"
|
12
|
+
require "redis_object/types"
|
13
|
+
require "redis_object/defaults"
|
14
|
+
require "redis_object/collection"
|
15
|
+
require "redis_object/indices"
|
16
|
+
require "redis_object/timestamps"
|
17
|
+
require "redis_object/experimental/history"
|
18
|
+
require "redis_object/ext/views"
|
19
|
+
require "redis_object/ext/view_caching"
|
20
|
+
require "redis_object/ext/triggers"
|
21
|
+
require "redis_object/ext/filters"
|
22
|
+
require "redis_object/ext/benchmark"
|
23
|
+
|
24
|
+
module Seabright
|
25
|
+
class RedisObject
|
26
|
+
|
27
|
+
include Seabright::Filters
|
28
|
+
include Seabright::ObjectBase
|
29
|
+
include Seabright::InheritanceTracking
|
30
|
+
include Seabright::CachedScripts
|
31
|
+
include Seabright::Storage
|
32
|
+
include Seabright::Keys
|
33
|
+
include Seabright::Types
|
34
|
+
include Seabright::DefaultValues
|
35
|
+
include Seabright::Collections
|
36
|
+
include Seabright::Indices
|
37
|
+
include Seabright::Views
|
38
|
+
include Seabright::ViewCaching
|
39
|
+
include Seabright::Timestamps
|
40
|
+
include Seabright::History
|
41
|
+
include Seabright::Triggers
|
42
|
+
include Seabright::Benchmark
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
::RedisObject = Seabright::RedisObject
|
@@ -0,0 +1,408 @@
|
|
1
|
+
module Seabright
|
2
|
+
module ObjectBase
|
3
|
+
|
4
|
+
def initialize(ident={})
|
5
|
+
if ident && (ident.class == String || (ident.class == Symbol && (ident = ident.to_s)))# && ident.gsub!(/.*:/,'') && ident.length > 0
|
6
|
+
load(ident.dup)
|
7
|
+
elsif ident && ident.class == Hash
|
8
|
+
ident[id_sym] ||= generate_id
|
9
|
+
if load(ident[id_sym])
|
10
|
+
mset(ident.dup)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
self
|
14
|
+
end
|
15
|
+
|
16
|
+
def new_id(complexity = 8)
|
17
|
+
self.class.new_id(complexity)
|
18
|
+
end
|
19
|
+
|
20
|
+
def generate_id
|
21
|
+
self.class.generate_id
|
22
|
+
end
|
23
|
+
|
24
|
+
def reserve(k)
|
25
|
+
self.class.reserve(k)
|
26
|
+
end
|
27
|
+
|
28
|
+
def to_json
|
29
|
+
Yajl::Encoder.encode(actual)
|
30
|
+
end
|
31
|
+
|
32
|
+
def id
|
33
|
+
@id || get(id_sym) || set(id_sym, generate_id)
|
34
|
+
end
|
35
|
+
|
36
|
+
def load(o_id)
|
37
|
+
@id = o_id
|
38
|
+
true
|
39
|
+
end
|
40
|
+
|
41
|
+
def save
|
42
|
+
set(:class, self.class.name)
|
43
|
+
set(id_sym,id.gsub(/.*:/,''))
|
44
|
+
set(:key, key)
|
45
|
+
store.sadd(self.class.plname, key)
|
46
|
+
store.del(reserve_key)
|
47
|
+
end
|
48
|
+
|
49
|
+
def delete!
|
50
|
+
store.del key
|
51
|
+
store.del hkey
|
52
|
+
store.del reserve_key
|
53
|
+
store.srem(self.class.plname, key)
|
54
|
+
# store.smembers(ref_key).each do |k|
|
55
|
+
# if self.class.find_by_key(k)
|
56
|
+
#
|
57
|
+
# end
|
58
|
+
# end
|
59
|
+
dereference_all!
|
60
|
+
nil
|
61
|
+
end
|
62
|
+
|
63
|
+
def dereference_all!
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
def raw
|
68
|
+
store.hgetall(hkey).inject({}) {|acc,(k,v)| acc[k.to_sym] = enforce_format(k,v); acc }
|
69
|
+
end
|
70
|
+
alias_method :inspect, :raw
|
71
|
+
alias_method :actual, :raw
|
72
|
+
|
73
|
+
def get(k)
|
74
|
+
cached_hash_values[k.to_s] ||= Proc.new {|key|
|
75
|
+
if v = store.hget(hkey, key.to_s)
|
76
|
+
define_setter_getter(key)
|
77
|
+
end
|
78
|
+
v
|
79
|
+
}.call(k)
|
80
|
+
end
|
81
|
+
|
82
|
+
def [](k)
|
83
|
+
get(k)
|
84
|
+
end
|
85
|
+
|
86
|
+
def is_set?(k)
|
87
|
+
store.hexists(hkey, k.to_s)
|
88
|
+
end
|
89
|
+
|
90
|
+
def mset(dat)
|
91
|
+
store.hmset(hkey, *(dat.inject([]){|acc,(k,v)| acc + [k,v] }))
|
92
|
+
cached_hash_values.merge!(dat)
|
93
|
+
dat.each do |k,v|
|
94
|
+
define_setter_getter(k)
|
95
|
+
end
|
96
|
+
dat
|
97
|
+
end
|
98
|
+
|
99
|
+
def define_setter_getter(key)
|
100
|
+
define_access(key) do
|
101
|
+
get(key)
|
102
|
+
end
|
103
|
+
define_access("#{key.to_s}=") do |val|
|
104
|
+
set(key,val)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def undefine_setter_getter(key)
|
109
|
+
undefine_access(key)
|
110
|
+
undefine_access("#{key.to_s}=")
|
111
|
+
end
|
112
|
+
|
113
|
+
def set(k,v)
|
114
|
+
store.hset(hkey, k.to_s, v.to_s)
|
115
|
+
cached_hash_values[k.to_s] = v
|
116
|
+
define_setter_getter(k)
|
117
|
+
v
|
118
|
+
end
|
119
|
+
|
120
|
+
def setnx(k,v)
|
121
|
+
if success = store.hsetnx(hkey, k.to_s, v.to_s)
|
122
|
+
cached_hash_values[k.to_s] = v
|
123
|
+
define_setter_getter(k)
|
124
|
+
end
|
125
|
+
success
|
126
|
+
end
|
127
|
+
|
128
|
+
def []=(k,v)
|
129
|
+
set(k,v)
|
130
|
+
end
|
131
|
+
|
132
|
+
def unset(*k)
|
133
|
+
store.hdel(hkey, k.map(&:to_s))
|
134
|
+
k.each do |ky|
|
135
|
+
cached_hash_values.delete ky.to_s
|
136
|
+
undefine_setter_getter(ky)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
private
|
141
|
+
|
142
|
+
SetPattern = /=$/.freeze
|
143
|
+
|
144
|
+
def method_missing(sym, *args, &block)
|
145
|
+
super if sym == :class
|
146
|
+
if sym.to_s =~ SetPattern
|
147
|
+
return super if args.size > 1
|
148
|
+
set(sym.to_s.gsub(SetPattern,'').to_sym,*args)
|
149
|
+
else
|
150
|
+
return super if !args.empty?
|
151
|
+
get(sym)
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def id_sym(cls=self.class.cname)
|
156
|
+
"#{cls.split('::').last.downcase}_id".to_sym
|
157
|
+
end
|
158
|
+
|
159
|
+
def load_all_hash_values
|
160
|
+
@cached_hash_values = store.hgetall(hkey)
|
161
|
+
cached_hash_values.keys.dup.each do |key|
|
162
|
+
next if key == "class"
|
163
|
+
define_setter_getter(key)
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
def cached_hash_values
|
168
|
+
@cached_hash_values ||= {}
|
169
|
+
end
|
170
|
+
|
171
|
+
def define_access(key,&block)
|
172
|
+
return if self.respond_to?(key.to_sym)
|
173
|
+
metaclass = class << self; self; end
|
174
|
+
metaclass.send(:define_method, key.to_sym, &block)
|
175
|
+
end
|
176
|
+
|
177
|
+
def undefine_access(key)
|
178
|
+
return unless self.respond_to?(key.to_sym)
|
179
|
+
metaclass = class << self; self; end
|
180
|
+
metaclass.send(:remove_method, key.to_sym)
|
181
|
+
end
|
182
|
+
|
183
|
+
module ClassMethods
|
184
|
+
|
185
|
+
def generate_id
|
186
|
+
v = new_id
|
187
|
+
while exists?(v) do
|
188
|
+
puts "[RedisObject] Collision at id: #{v}" if Debug.verbose?
|
189
|
+
v = new_id
|
190
|
+
end
|
191
|
+
puts "[RedisObject] Reserving key: #{v}" if Debug.verbose?
|
192
|
+
reserve(v)
|
193
|
+
v
|
194
|
+
end
|
195
|
+
|
196
|
+
def reserve(k)
|
197
|
+
store.set(reserve_key(k),Time.now.to_s)
|
198
|
+
end
|
199
|
+
|
200
|
+
def new_id(complexity = 8)
|
201
|
+
rand(36**complexity).to_s(36)
|
202
|
+
end
|
203
|
+
|
204
|
+
def cname
|
205
|
+
self.name
|
206
|
+
end
|
207
|
+
|
208
|
+
def plname
|
209
|
+
cname.pluralize
|
210
|
+
end
|
211
|
+
|
212
|
+
def all
|
213
|
+
Enumerator.new do |y|
|
214
|
+
store.smembers(plname).each do |member|
|
215
|
+
if a = find_by_key(hkey(member))
|
216
|
+
y << a
|
217
|
+
else
|
218
|
+
puts "[#{name}] Object listed but not found: #{member}" if DEBUG
|
219
|
+
store.srem(plname,member)
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
def recollect!
|
226
|
+
store.keys("#{name}:*_h").each do |ky|
|
227
|
+
store.sadd(plname,ky.gsub(/_h$/,''))
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
def first
|
232
|
+
if m = store.smembers(plname)
|
233
|
+
self.grab(m.first)
|
234
|
+
else
|
235
|
+
nil
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
def each
|
240
|
+
all.each do |o|
|
241
|
+
yield o
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
RedisObject::ScriptSources::Matcher = "local itms = redis.call('SMEMBERS',KEYS[1])
|
246
|
+
local out = {}
|
247
|
+
local val
|
248
|
+
local pattern
|
249
|
+
for i, v in ipairs(itms) do
|
250
|
+
val = redis.call('HGET',v..'_h',ARGV[1])
|
251
|
+
if ARGV[2]:find('^pattern:') then
|
252
|
+
pattern = ARGV[2]:gsub('^pattern:','')
|
253
|
+
if val:match(pattern) ~= nil then
|
254
|
+
table.insert(out,itms[i])
|
255
|
+
end
|
256
|
+
else
|
257
|
+
if val == ARGV[2] then
|
258
|
+
table.insert(out,itms[i])
|
259
|
+
end
|
260
|
+
end
|
261
|
+
end
|
262
|
+
return out".gsub(/\t/,'').freeze
|
263
|
+
|
264
|
+
RedisObject::ScriptSources::MultiMatcher = "local itms = redis.call('SMEMBERS',KEYS[1])
|
265
|
+
local out = {}
|
266
|
+
local matchers = {}
|
267
|
+
local matcher = {}
|
268
|
+
local mod
|
269
|
+
for i=1,#ARGV do
|
270
|
+
mod = i % 2
|
271
|
+
if mod == 1 then
|
272
|
+
matcher[1] = ARGV[i]
|
273
|
+
else
|
274
|
+
matcher[2] = ARGV[i]
|
275
|
+
table.insert(matchers,matcher)
|
276
|
+
matcher = {}
|
277
|
+
end
|
278
|
+
end
|
279
|
+
local val
|
280
|
+
local good
|
281
|
+
local pattern
|
282
|
+
for i, v in ipairs(itms) do
|
283
|
+
good = true
|
284
|
+
for n=1,#matchers do
|
285
|
+
val = redis.call('HGET',v..'_h',matchers[n][1])
|
286
|
+
if val then
|
287
|
+
if matchers[n][2]:find('^pattern:') then
|
288
|
+
pattern = matchers[n][2]:gsub('^pattern:','')
|
289
|
+
if val:match(pattern) then
|
290
|
+
good = good
|
291
|
+
else
|
292
|
+
good = false
|
293
|
+
end
|
294
|
+
else
|
295
|
+
if val ~= matchers[n][2] then
|
296
|
+
good = false
|
297
|
+
end
|
298
|
+
end
|
299
|
+
else
|
300
|
+
good = false
|
301
|
+
end
|
302
|
+
end
|
303
|
+
if good == true then
|
304
|
+
table.insert(out,itms[i])
|
305
|
+
end
|
306
|
+
end
|
307
|
+
return out".gsub(/\t/,'').freeze
|
308
|
+
|
309
|
+
def match(pkt)
|
310
|
+
Enumerator.new do |y|
|
311
|
+
run_script(pkt.keys.count > 1 ? :MultiMatcher : :Matcher,[plname],pkt.flatten.map{|i| i.is_a?(Regexp) ? convert_regex_to_lua(i) : i.to_s }).each do |k|
|
312
|
+
y << find(k)
|
313
|
+
end
|
314
|
+
end
|
315
|
+
end
|
316
|
+
|
317
|
+
def convert_regex_to_lua(reg)
|
318
|
+
"pattern:#{reg.source.gsub("\\","")}"
|
319
|
+
end
|
320
|
+
|
321
|
+
def grab(ident)
|
322
|
+
case ident
|
323
|
+
when String, Symbol
|
324
|
+
return store.exists(self.hkey(ident.to_s)) ? self.new(ident.to_s) : nil
|
325
|
+
when Hash
|
326
|
+
return match(ident)
|
327
|
+
end
|
328
|
+
nil
|
329
|
+
end
|
330
|
+
|
331
|
+
def find(ident)
|
332
|
+
grab(ident)
|
333
|
+
end
|
334
|
+
|
335
|
+
def exists?(k)
|
336
|
+
store.exists(self.hkey(k)) || store.exists(self.reserve_key(k))
|
337
|
+
end
|
338
|
+
|
339
|
+
def create(ident={})
|
340
|
+
obj = new(ident)
|
341
|
+
obj.save
|
342
|
+
obj
|
343
|
+
end
|
344
|
+
|
345
|
+
# def dump
|
346
|
+
# out = []
|
347
|
+
# each do |obj|
|
348
|
+
# out << obj.dump
|
349
|
+
# end
|
350
|
+
# out.join("\n")
|
351
|
+
# end
|
352
|
+
|
353
|
+
def use_dbnum(db=0)
|
354
|
+
@dbnum = db
|
355
|
+
end
|
356
|
+
|
357
|
+
def dbnum
|
358
|
+
@dbnum ||= 0
|
359
|
+
end
|
360
|
+
|
361
|
+
def find_by_key(k)
|
362
|
+
if store.exists(k) && (cls = store.hget(k,:class))
|
363
|
+
return deep_const_get(cls.to_sym).new(store.hget(k,id_sym(cls)))
|
364
|
+
end
|
365
|
+
nil
|
366
|
+
end
|
367
|
+
|
368
|
+
def deep_const_get(const)
|
369
|
+
if Symbol === const
|
370
|
+
const = const.to_s
|
371
|
+
else
|
372
|
+
const = const.to_str.dup
|
373
|
+
end
|
374
|
+
if const.sub!(/^::/, '')
|
375
|
+
base = Object
|
376
|
+
else
|
377
|
+
base = self
|
378
|
+
end
|
379
|
+
const.split(/::/).inject(base) { |mod, name| mod.const_get(name) }
|
380
|
+
end
|
381
|
+
|
382
|
+
def save_all
|
383
|
+
all.each do |obj|
|
384
|
+
obj.save
|
385
|
+
end
|
386
|
+
true
|
387
|
+
end
|
388
|
+
|
389
|
+
def id_sym(cls=cname)
|
390
|
+
"#{cls.split('::').last.downcase}_id".to_sym
|
391
|
+
end
|
392
|
+
|
393
|
+
def describe
|
394
|
+
all_keys.inject({}) do |acc,(k,v)|
|
395
|
+
acc[k.to_sym] ||= [:string, 0]
|
396
|
+
acc[k.to_sym][1] += 1
|
397
|
+
acc
|
398
|
+
end
|
399
|
+
end
|
400
|
+
|
401
|
+
end
|
402
|
+
|
403
|
+
def self.included(base)
|
404
|
+
base.extend(ClassMethods)
|
405
|
+
end
|
406
|
+
|
407
|
+
end
|
408
|
+
end
|