chimera 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/History.txt +4 -0
- data/Manifest.txt +57 -0
- data/PostInstall.txt +7 -0
- data/README.rdoc +114 -0
- data/Rakefile +30 -0
- data/doc/NOTES +11 -0
- data/doc/examples/config.yml +16 -0
- data/doc/redis6379.conf +132 -0
- data/lib/chimera.rb +33 -0
- data/lib/chimera/associations.rb +146 -0
- data/lib/chimera/attributes.rb +52 -0
- data/lib/chimera/base.rb +95 -0
- data/lib/chimera/config.rb +9 -0
- data/lib/chimera/error.rb +12 -0
- data/lib/chimera/finders.rb +49 -0
- data/lib/chimera/geo_indexes.rb +76 -0
- data/lib/chimera/indexes.rb +177 -0
- data/lib/chimera/persistence.rb +70 -0
- data/lib/chimera/redis_objects.rb +345 -0
- data/lib/redis.rb +373 -0
- data/lib/redis/counter.rb +94 -0
- data/lib/redis/dist_redis.rb +149 -0
- data/lib/redis/hash_ring.rb +135 -0
- data/lib/redis/helpers/core_commands.rb +46 -0
- data/lib/redis/helpers/serialize.rb +25 -0
- data/lib/redis/list.rb +122 -0
- data/lib/redis/lock.rb +83 -0
- data/lib/redis/objects.rb +100 -0
- data/lib/redis/objects/counters.rb +132 -0
- data/lib/redis/objects/lists.rb +45 -0
- data/lib/redis/objects/locks.rb +71 -0
- data/lib/redis/objects/sets.rb +46 -0
- data/lib/redis/objects/values.rb +56 -0
- data/lib/redis/pipeline.rb +21 -0
- data/lib/redis/set.rb +156 -0
- data/lib/redis/value.rb +35 -0
- data/lib/riak_raw.rb +100 -0
- data/lib/typhoeus.rb +55 -0
- data/lib/typhoeus/.gitignore +1 -0
- data/lib/typhoeus/easy.rb +253 -0
- data/lib/typhoeus/filter.rb +28 -0
- data/lib/typhoeus/hydra.rb +210 -0
- data/lib/typhoeus/multi.rb +34 -0
- data/lib/typhoeus/remote.rb +306 -0
- data/lib/typhoeus/remote_method.rb +108 -0
- data/lib/typhoeus/remote_proxy_object.rb +48 -0
- data/lib/typhoeus/request.rb +124 -0
- data/lib/typhoeus/response.rb +39 -0
- data/lib/typhoeus/service.rb +20 -0
- data/script/console +10 -0
- data/script/destroy +14 -0
- data/script/generate +14 -0
- data/test/models.rb +49 -0
- data/test/test_chimera.rb +238 -0
- data/test/test_helper.rb +7 -0
- metadata +243 -0
@@ -0,0 +1,52 @@
|
|
1
|
+
module Chimera
|
2
|
+
module Attributes
|
3
|
+
def self.included(base)
|
4
|
+
base.send :extend, ClassMethods
|
5
|
+
base.send :include, InstanceMethods
|
6
|
+
end
|
7
|
+
|
8
|
+
module ClassMethods
|
9
|
+
def defined_attributes
|
10
|
+
@defined_attributes || {}
|
11
|
+
end
|
12
|
+
|
13
|
+
# available types include: model
|
14
|
+
def attribute(name, type = nil, extra_opts={})
|
15
|
+
@defined_attributes ||= {}
|
16
|
+
@defined_attributes[name.to_sym] = [type, extra_opts]
|
17
|
+
define_method("#{name}") do
|
18
|
+
return nil unless @attributes
|
19
|
+
if type == :model
|
20
|
+
@cached_attributes ||= {}
|
21
|
+
@cached_attributes[name.to_sym] ||= begin
|
22
|
+
model_id = @attributes[name.to_sym]
|
23
|
+
klass = extra_opts[:class]
|
24
|
+
if model_id && klass
|
25
|
+
eval(klass.to_s.camelize).find(model_id)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
else
|
29
|
+
@attributes[name.to_sym]
|
30
|
+
end
|
31
|
+
end
|
32
|
+
define_method("#{name}=") do |val|
|
33
|
+
return nil unless @attributes
|
34
|
+
if type == :model
|
35
|
+
@cached_attributes ||= {}
|
36
|
+
@cached_attributes.delete(name.to_sym)
|
37
|
+
if val.respond_to?(:id)
|
38
|
+
@attributes[name.to_sym] = val.id
|
39
|
+
else
|
40
|
+
@attributes.delete(name.to_sym)
|
41
|
+
end
|
42
|
+
else @attributes
|
43
|
+
@attributes[name.to_sym] = val
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end # ClassMethods
|
48
|
+
|
49
|
+
module InstanceMethods
|
50
|
+
end # InstanceMethods
|
51
|
+
end
|
52
|
+
end
|
data/lib/chimera/base.rb
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
module Chimera
|
2
|
+
def self.config_path=(path)
|
3
|
+
@config_path = path
|
4
|
+
@config = YAML.load_file(@config_path)
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.config
|
8
|
+
@config || raise(Chimera::Error::MissingConfig)
|
9
|
+
end
|
10
|
+
|
11
|
+
class Base
|
12
|
+
include Chimera::Attributes
|
13
|
+
include Chimera::Indexes
|
14
|
+
include Chimera::GeoIndexes
|
15
|
+
include Chimera::Associations
|
16
|
+
include Chimera::RedisObjects
|
17
|
+
include Chimera::Finders
|
18
|
+
include Chimera::Persistence
|
19
|
+
include ActiveModel::Validations
|
20
|
+
|
21
|
+
extend ActiveModel::Naming
|
22
|
+
extend ActiveModel::Callbacks
|
23
|
+
define_model_callbacks :create, :save, :destroy
|
24
|
+
|
25
|
+
attr_accessor :id, :attributes, :orig_attributes, :riak_response, :associations
|
26
|
+
|
27
|
+
def self.use_config(key)
|
28
|
+
@config = (Chimera.config[key.to_sym] || raise(Chimera::Error::MissingConfig,":#{key}"))
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.config
|
32
|
+
@config ||= (Chimera.config[:default] || raise(Chimera::Error::MissingConfig,":default"))
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.connection(server)
|
36
|
+
Thread.current["Chimera::#{self.to_s}::#{server}::connection"] ||= begin
|
37
|
+
case server.to_sym
|
38
|
+
when :redis
|
39
|
+
Redis.new(self.config[:redis])
|
40
|
+
when :riak_raw
|
41
|
+
RiakRaw::Client.new(self.config[:riak_raw][:host], self.config[:riak_raw][:port])
|
42
|
+
else
|
43
|
+
nil
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.bucket_key
|
49
|
+
self.to_s
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.bucket(keys=false)
|
53
|
+
self.connection(:riak_raw).bucket(self.bucket_key,keys)
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.new_uuid
|
57
|
+
UUIDTools::UUID.random_create.to_s
|
58
|
+
end
|
59
|
+
|
60
|
+
def inspect
|
61
|
+
"#<#{self.to_s}: @id=#{self.id}, @new=#{@new}>"
|
62
|
+
end
|
63
|
+
|
64
|
+
def ==(obj)
|
65
|
+
obj.class.to_s == self.class.to_s &&
|
66
|
+
!obj.new? && !self.new? &&
|
67
|
+
obj.id == self.id
|
68
|
+
end
|
69
|
+
|
70
|
+
def <=>(obj)
|
71
|
+
self.id.to_s <=> obj.id.to_s
|
72
|
+
end
|
73
|
+
|
74
|
+
def initialize(attributes={},id=nil,is_new=true)
|
75
|
+
@attributes = attributes
|
76
|
+
@orig_attributes = @attributes.clone
|
77
|
+
@id = id
|
78
|
+
@new = is_new
|
79
|
+
end
|
80
|
+
|
81
|
+
def id=(val)
|
82
|
+
if self.new?
|
83
|
+
@id = val
|
84
|
+
else
|
85
|
+
raise(Chimera::Error::AttemptToModifyId)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
protected
|
90
|
+
|
91
|
+
def read_attribute_for_validation(key)
|
92
|
+
@attributes[key.to_sym]
|
93
|
+
end
|
94
|
+
end # Base
|
95
|
+
end # Chimera
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module Chimera
|
2
|
+
class Error < RuntimeError
|
3
|
+
class MissingConfig < Chimera::Error; end
|
4
|
+
class UniqueConstraintViolation < Chimera::Error; end
|
5
|
+
class SaveWithoutId < Chimera::Error; end
|
6
|
+
class MissingId < Chimera::Error; end
|
7
|
+
class ValidationErrors < Chimera::Error; end
|
8
|
+
class AttemptToModifyId < Chimera::Error; end
|
9
|
+
class AssociationClassMismatch < Chimera::Error; end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Chimera
|
2
|
+
module Finders
|
3
|
+
def self.included(base)
|
4
|
+
base.send :extend, ClassMethods
|
5
|
+
base.send :include, InstanceMethods
|
6
|
+
end
|
7
|
+
|
8
|
+
module ClassMethods
|
9
|
+
def each
|
10
|
+
self.find_with_index(:all) { |obj| yield(obj) }
|
11
|
+
end
|
12
|
+
|
13
|
+
def find(key)
|
14
|
+
find_many(key)[0]
|
15
|
+
end
|
16
|
+
|
17
|
+
def find_many(keys)
|
18
|
+
keys = Array(keys)
|
19
|
+
found = []
|
20
|
+
threads = []
|
21
|
+
keys.each do |key|
|
22
|
+
threads << Thread.new do
|
23
|
+
if key
|
24
|
+
resp = self.connection(:riak_raw).fetch(self.to_s, key)
|
25
|
+
if resp.code == 200
|
26
|
+
if resp.body and yaml_hash = YAML.load(resp.body)
|
27
|
+
hash = {}
|
28
|
+
yaml_hash.each { |k,v| hash[k.to_sym] = v }
|
29
|
+
obj = self.new(hash,key,false)
|
30
|
+
obj.riak_response = resp
|
31
|
+
found << obj
|
32
|
+
else
|
33
|
+
obj = self.new({},key,false)
|
34
|
+
obj.riak_response = resp
|
35
|
+
found << obj
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end # Thread.new
|
40
|
+
end # keys.each
|
41
|
+
threads.each { |th| th.join }
|
42
|
+
found
|
43
|
+
end # find_many
|
44
|
+
end # ClassMethods
|
45
|
+
|
46
|
+
module InstanceMethods
|
47
|
+
end # InstanceMethods
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
module Chimera
|
2
|
+
module GeoIndexes
|
3
|
+
def self.included(base)
|
4
|
+
base.send :extend, ClassMethods
|
5
|
+
base.send :include, InstanceMethods
|
6
|
+
end
|
7
|
+
|
8
|
+
module ClassMethods
|
9
|
+
def key_for_geo_index(type, name, lat, lon, step_size=0.05)
|
10
|
+
step_size ||= 0.05
|
11
|
+
case type.to_sym
|
12
|
+
when :geo then
|
13
|
+
lat = geo_square_coord(lat)
|
14
|
+
lon = geo_square_coord(lon)
|
15
|
+
"#{self.to_s}::Indexes::#{type}::#{name}::#{step_size}::#{lat}::#{lon}"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def find_with_geo_index(name, opts_or_query)
|
20
|
+
if props = self.defined_indexes[name.to_sym]
|
21
|
+
case props[:type]
|
22
|
+
when :geo then
|
23
|
+
step_size = props[:step_size]
|
24
|
+
num_steps = opts_or_query[:steps] || 5
|
25
|
+
steps = [50,num_steps].min * step_size
|
26
|
+
lat, lon = opts_or_query[:coordinate]
|
27
|
+
union_keys = []
|
28
|
+
curr_lat = lat - steps
|
29
|
+
while curr_lat < lat+steps
|
30
|
+
curr_lon = lon - steps
|
31
|
+
while curr_lon < lon+steps
|
32
|
+
union_keys << key_for_geo_index(:geo,name,curr_lat,curr_lon,step_size)
|
33
|
+
curr_lon += step_size
|
34
|
+
end
|
35
|
+
curr_lat += step_size
|
36
|
+
end
|
37
|
+
keys = self.connection(:redis).sunion(union_keys.join(" "))
|
38
|
+
find_many(keys)
|
39
|
+
end # case
|
40
|
+
end # if props =
|
41
|
+
end
|
42
|
+
|
43
|
+
def geo_square_coord(lat_or_lon, step=0.05)
|
44
|
+
i = (lat_or_lon*1000000).floor
|
45
|
+
i += (step/2)*1000000
|
46
|
+
(i - (i % (step * 1000000)))/1000000
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
module InstanceMethods
|
51
|
+
def destroy_geo_indexes
|
52
|
+
self.class.defined_indexes.each do |name, props|
|
53
|
+
case props[:type]
|
54
|
+
when :geo then
|
55
|
+
if val = @orig_attributes[name.to_sym] and val.is_a?(Array)
|
56
|
+
index_key = self.class.key_for_geo_index(:geo, name, val[0], val[1], props[:step_size])
|
57
|
+
self.class.connection(:redis).srem(index_key, self.id)
|
58
|
+
end
|
59
|
+
end # case props[:type]
|
60
|
+
end # self.class.defined_indexes
|
61
|
+
end # destroy_geo_indexes
|
62
|
+
|
63
|
+
def create_geo_indexes
|
64
|
+
self.class.defined_indexes.each do |name, props|
|
65
|
+
case props[:type]
|
66
|
+
when :geo then
|
67
|
+
if val = @attributes[name.to_sym] and val.is_a?(Array)
|
68
|
+
index_key = self.class.key_for_geo_index(:geo, name, val[0], val[1], props[:step_size])
|
69
|
+
self.class.connection(:redis).sadd(index_key, self.id)
|
70
|
+
end
|
71
|
+
end # case props[:type]
|
72
|
+
end # self.class.defined_indexes
|
73
|
+
end # create_geo_indexes
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,177 @@
|
|
1
|
+
module Chimera
|
2
|
+
module Indexes
|
3
|
+
SEARCH_EXCLUDE_LIST = %w(a an and as at but by for in into of on onto to the)
|
4
|
+
|
5
|
+
def self.included(base)
|
6
|
+
base.send :extend, ClassMethods
|
7
|
+
base.send :include, InstanceMethods
|
8
|
+
end
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
def defined_indexes
|
12
|
+
@defined_indexes || {}
|
13
|
+
end
|
14
|
+
|
15
|
+
# available types include:
|
16
|
+
# find, unique, search, geo
|
17
|
+
def index(name, type = :find)
|
18
|
+
@defined_indexes ||= {}
|
19
|
+
@defined_indexes[name.to_sym] = type
|
20
|
+
end
|
21
|
+
|
22
|
+
def find_with_index(name, opts_or_query=nil)
|
23
|
+
if opts_or_query.is_a?(Hash)
|
24
|
+
q = opts_or_query[:q].to_s
|
25
|
+
else
|
26
|
+
q = opts_or_query.to_s
|
27
|
+
end
|
28
|
+
if name.to_sym == :all
|
29
|
+
llen = self.connection(:redis).llen(self.key_for_all_index)
|
30
|
+
curr = 0
|
31
|
+
while(curr < llen)
|
32
|
+
keys = self.connection(:redis).lrange(self.key_for_all_index, curr, curr+9).compact
|
33
|
+
find_many(keys).each { |obj| yield(obj) }
|
34
|
+
curr += 10
|
35
|
+
end
|
36
|
+
elsif props = self.defined_indexes[name.to_sym]
|
37
|
+
case props[:type]
|
38
|
+
when :find then
|
39
|
+
if q and !q.blank?
|
40
|
+
index_key = self.key_for_index(:find, name, q)
|
41
|
+
self.find_many(self.connection(:redis).smembers(index_key))
|
42
|
+
end
|
43
|
+
when :unique then
|
44
|
+
if q and !q.blank?
|
45
|
+
index_key = self.key_for_index(:unique, name, q)
|
46
|
+
Array(self.find(self.connection(:redis).get(index_key)))
|
47
|
+
end
|
48
|
+
when :search then
|
49
|
+
keys = []
|
50
|
+
q.split(" ").each do |word|
|
51
|
+
word = word.downcase.stem
|
52
|
+
next if Chimera::Indexes::SEARCH_EXCLUDE_LIST.include?(word)
|
53
|
+
keys << self.key_for_index(:search, name, word)
|
54
|
+
end
|
55
|
+
if keys.size > 0
|
56
|
+
if opts_or_query.is_a?(Hash) and opts_or_query[:type] == :union
|
57
|
+
self.find_many(self.connection(:redis).sunion(keys.join(" ")))
|
58
|
+
else
|
59
|
+
self.find_many(self.connection(:redis).sinter(keys.join(" ")))
|
60
|
+
end
|
61
|
+
end
|
62
|
+
when :geo then
|
63
|
+
find_with_geo_index(name, opts_or_query)
|
64
|
+
end # case
|
65
|
+
end # if props
|
66
|
+
end
|
67
|
+
|
68
|
+
def key_for_index(type, name, val)
|
69
|
+
case type.to_sym
|
70
|
+
when :find, :unique, :search then
|
71
|
+
"#{self.to_s}::Indexes::#{type}::#{name}::#{digest(val)}"
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def key_for_all_index
|
76
|
+
"#{self.to_s}::Indexes::All"
|
77
|
+
end
|
78
|
+
|
79
|
+
def digest(val)
|
80
|
+
Digest::SHA1.hexdigest(val)
|
81
|
+
end
|
82
|
+
end # ClassMethods
|
83
|
+
|
84
|
+
module InstanceMethods
|
85
|
+
def destroy_indexes
|
86
|
+
remove_from_all_index
|
87
|
+
self.class.defined_indexes.each do |name, props|
|
88
|
+
case props[:type]
|
89
|
+
when :find then
|
90
|
+
if val = @orig_attributes[name.to_sym] and !val.blank?
|
91
|
+
index_key = self.class.key_for_index(:find, name,val.to_s)
|
92
|
+
self.class.connection(:redis).srem(index_key, self.id)
|
93
|
+
end
|
94
|
+
when :unique then
|
95
|
+
if val = @orig_attributes[name.to_sym] and !val.blank?
|
96
|
+
index_key = self.class.key_for_index(:unique, name,val.to_s)
|
97
|
+
self.class.connection(:redis).del(index_key)
|
98
|
+
end
|
99
|
+
when :search then
|
100
|
+
if val = @orig_attributes[name.to_sym] and !val.blank?
|
101
|
+
val.to_s.split(" ").each do |word|
|
102
|
+
word = word.downcase.stem
|
103
|
+
next if Chimera::Indexes::SEARCH_EXCLUDE_LIST.include?(word)
|
104
|
+
index_key = self.class.key_for_index(:search, name, word)
|
105
|
+
self.class.connection(:redis).srem(index_key, self.id)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
destroy_geo_indexes
|
111
|
+
end
|
112
|
+
|
113
|
+
def check_index_constraints
|
114
|
+
self.class.defined_indexes.each do |name, props|
|
115
|
+
case props[:type]
|
116
|
+
when :unique then
|
117
|
+
if val = @attributes[name.to_sym] and !val.blank?
|
118
|
+
index_key = self.class.key_for_index(:unique, name,val.to_s)
|
119
|
+
if k = self.class.connection(:redis).get(index_key)
|
120
|
+
if k.to_s != self.id.to_s
|
121
|
+
raise(Chimera::Error::UniqueConstraintViolation, val)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end # if val
|
125
|
+
end # case
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def create_indexes
|
130
|
+
add_to_all_index
|
131
|
+
self.class.defined_indexes.each do |name, props|
|
132
|
+
case props[:type]
|
133
|
+
when :find then
|
134
|
+
if val = @attributes[name.to_sym] and !val.blank?
|
135
|
+
index_key = self.class.key_for_index(:find, name, val.to_s)
|
136
|
+
self.class.connection(:redis).sadd(index_key, self.id)
|
137
|
+
end
|
138
|
+
when :unique then
|
139
|
+
if val = @attributes[name.to_sym] and !val.blank?
|
140
|
+
index_key = self.class.key_for_index(:unique, name,val.to_s)
|
141
|
+
if self.class.connection(:redis).exists(index_key)
|
142
|
+
raise(Chimera::Error::UniqueConstraintViolation, val)
|
143
|
+
else
|
144
|
+
self.class.connection(:redis).set(index_key, self.id)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
when :search then
|
148
|
+
if val = @attributes[name.to_sym] and !val.blank?
|
149
|
+
val.to_s.split(" ").each do |word|
|
150
|
+
word = word.downcase.stem
|
151
|
+
next if Chimera::Indexes::SEARCH_EXCLUDE_LIST.include?(word)
|
152
|
+
index_key = self.class.key_for_index(:search, name, word)
|
153
|
+
self.class.connection(:redis).sadd(index_key, self.id)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
create_geo_indexes
|
159
|
+
end
|
160
|
+
|
161
|
+
def update_indexes
|
162
|
+
destroy_indexes
|
163
|
+
destroy_geo_indexes
|
164
|
+
create_indexes
|
165
|
+
create_geo_indexes
|
166
|
+
end
|
167
|
+
|
168
|
+
def remove_from_all_index
|
169
|
+
self.class.connection(:redis).lrem(self.class.key_for_all_index, 0, self.id)
|
170
|
+
end
|
171
|
+
|
172
|
+
def add_to_all_index
|
173
|
+
self.class.connection(:redis).lpush(self.class.key_for_all_index, self.id)
|
174
|
+
end
|
175
|
+
end # InstanceMethods
|
176
|
+
end
|
177
|
+
end
|