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.
Files changed (56) hide show
  1. data/History.txt +4 -0
  2. data/Manifest.txt +57 -0
  3. data/PostInstall.txt +7 -0
  4. data/README.rdoc +114 -0
  5. data/Rakefile +30 -0
  6. data/doc/NOTES +11 -0
  7. data/doc/examples/config.yml +16 -0
  8. data/doc/redis6379.conf +132 -0
  9. data/lib/chimera.rb +33 -0
  10. data/lib/chimera/associations.rb +146 -0
  11. data/lib/chimera/attributes.rb +52 -0
  12. data/lib/chimera/base.rb +95 -0
  13. data/lib/chimera/config.rb +9 -0
  14. data/lib/chimera/error.rb +12 -0
  15. data/lib/chimera/finders.rb +49 -0
  16. data/lib/chimera/geo_indexes.rb +76 -0
  17. data/lib/chimera/indexes.rb +177 -0
  18. data/lib/chimera/persistence.rb +70 -0
  19. data/lib/chimera/redis_objects.rb +345 -0
  20. data/lib/redis.rb +373 -0
  21. data/lib/redis/counter.rb +94 -0
  22. data/lib/redis/dist_redis.rb +149 -0
  23. data/lib/redis/hash_ring.rb +135 -0
  24. data/lib/redis/helpers/core_commands.rb +46 -0
  25. data/lib/redis/helpers/serialize.rb +25 -0
  26. data/lib/redis/list.rb +122 -0
  27. data/lib/redis/lock.rb +83 -0
  28. data/lib/redis/objects.rb +100 -0
  29. data/lib/redis/objects/counters.rb +132 -0
  30. data/lib/redis/objects/lists.rb +45 -0
  31. data/lib/redis/objects/locks.rb +71 -0
  32. data/lib/redis/objects/sets.rb +46 -0
  33. data/lib/redis/objects/values.rb +56 -0
  34. data/lib/redis/pipeline.rb +21 -0
  35. data/lib/redis/set.rb +156 -0
  36. data/lib/redis/value.rb +35 -0
  37. data/lib/riak_raw.rb +100 -0
  38. data/lib/typhoeus.rb +55 -0
  39. data/lib/typhoeus/.gitignore +1 -0
  40. data/lib/typhoeus/easy.rb +253 -0
  41. data/lib/typhoeus/filter.rb +28 -0
  42. data/lib/typhoeus/hydra.rb +210 -0
  43. data/lib/typhoeus/multi.rb +34 -0
  44. data/lib/typhoeus/remote.rb +306 -0
  45. data/lib/typhoeus/remote_method.rb +108 -0
  46. data/lib/typhoeus/remote_proxy_object.rb +48 -0
  47. data/lib/typhoeus/request.rb +124 -0
  48. data/lib/typhoeus/response.rb +39 -0
  49. data/lib/typhoeus/service.rb +20 -0
  50. data/script/console +10 -0
  51. data/script/destroy +14 -0
  52. data/script/generate +14 -0
  53. data/test/models.rb +49 -0
  54. data/test/test_chimera.rb +238 -0
  55. data/test/test_helper.rb +7 -0
  56. 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
@@ -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,9 @@
1
+ module Chimera
2
+ module Config
3
+ module ClassMethods
4
+ end
5
+
6
+ module InstanceMethods
7
+ end
8
+ end
9
+ end
@@ -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