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,124 @@
1
+ module Typhoeus
2
+ class Request
3
+ attr_accessor :method, :params, :body, :headers, :timeout, :user_agent, :response, :cache_timeout, :follow_location, :max_redirects, :proxy, :disable_ssl_peer_verification
4
+ attr_reader :url
5
+
6
+ def initialize(url, options = {})
7
+ @method = options[:method] || :get
8
+ @params = options[:params]
9
+ @body = options[:body]
10
+ @timeout = options[:timeout]
11
+ @headers = options[:headers] || {}
12
+ @user_agent = options[:user_agent] || Typhoeus::USER_AGENT
13
+ @cache_timeout = options[:cache_timeout]
14
+ @follow_location = options[:follow_location]
15
+ @max_redirects = options[:max_redirects]
16
+ @proxy = options[:proxy]
17
+ @disable_ssl_peer_verification = options[:disable_ssl_peer_verification]
18
+
19
+ if @method == :post
20
+ @url = url
21
+ else
22
+ @url = @params ? "#{url}?#{params_string}" : url
23
+ end
24
+ @on_complete = nil
25
+ @after_complete = nil
26
+ @handled_response = nil
27
+ end
28
+
29
+ def host
30
+ slash_location = @url.index('/', 8)
31
+ if slash_location
32
+ @url.slice(0, slash_location)
33
+ else
34
+ query_string_location = @url.index('?')
35
+ return query_string_location ? @url.slice(0, query_string_location) : @url
36
+ end
37
+ end
38
+
39
+ def headers
40
+ @headers["User-Agent"] = @user_agent
41
+ @headers
42
+ end
43
+
44
+ def params_string
45
+ params.keys.sort.collect do |k|
46
+ value = params[k]
47
+ if value.is_a? Hash
48
+ value.keys.collect {|sk| Rack::Utils.escape("#{k}[#{sk}]") + "=" + Rack::Utils.escape(value[sk].to_s)}
49
+ elsif value.is_a? Array
50
+ key = Rack::Utils.escape(k.to_s)
51
+ value.collect { |v| "#{key}=#{Rack::Utils.escape(v.to_s)}" }.join('&')
52
+ else
53
+ "#{Rack::Utils.escape(k.to_s)}=#{Rack::Utils.escape(params[k].to_s)}"
54
+ end
55
+ end.flatten.join("&")
56
+ end
57
+
58
+ def on_complete(&block)
59
+ @on_complete = block
60
+ end
61
+
62
+ def on_complete=(proc)
63
+ @on_complete = proc
64
+ end
65
+
66
+ def after_complete(&block)
67
+ @after_complete = block
68
+ end
69
+
70
+ def after_complete=(proc)
71
+ @after_complete = proc
72
+ end
73
+
74
+ def call_handlers
75
+ if @on_complete
76
+ @handled_response = @on_complete.call(response)
77
+ call_after_complete
78
+ end
79
+ end
80
+
81
+ def call_after_complete
82
+ @after_complete.call(@handled_response) if @after_complete
83
+ end
84
+
85
+ def handled_response=(val)
86
+ @handled_response = val
87
+ end
88
+
89
+ def handled_response
90
+ @handled_response || response
91
+ end
92
+
93
+ def cache_key
94
+ Digest::SHA1.hexdigest(url)
95
+ end
96
+
97
+ def self.run(url, params)
98
+ r = new(url, params)
99
+ Typhoeus::Hydra.hydra.queue r
100
+ Typhoeus::Hydra.hydra.run
101
+ r.response
102
+ end
103
+
104
+ def self.get(url, params = {})
105
+ run(url, params.merge(:method => :get))
106
+ end
107
+
108
+ def self.post(url, params = {})
109
+ run(url, params.merge(:method => :post))
110
+ end
111
+
112
+ def self.put(url, params = {})
113
+ run(url, params.merge(:method => :put))
114
+ end
115
+
116
+ def self.delete(url, params = {})
117
+ run(url, params.merge(:method => :delete))
118
+ end
119
+
120
+ def self.head(url, params = {})
121
+ run(url, params.merge(:method => :head))
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,39 @@
1
+ module Typhoeus
2
+ class Response
3
+ attr_accessor :request
4
+ attr_reader :code, :headers, :body, :time,
5
+ :requested_url, :requested_remote_method,
6
+ :requested_http_method, :start_time
7
+
8
+ def initialize(params = {})
9
+ @code = params[:code]
10
+ @headers = params[:headers]
11
+ @body = params[:body]
12
+ @time = params[:time]
13
+ @requested_url = params[:requested_url]
14
+ @requested_http_method = params[:requested_http_method]
15
+ @start_time = params[:start_time]
16
+ @request = params[:request]
17
+ end
18
+
19
+ def headers_hash
20
+ headers.split("\n").map {|o| o.strip}.inject({}) do |hash, o|
21
+ if o.empty?
22
+ hash
23
+ else
24
+ i = o.index(":") || o.size
25
+ key = o.slice(0, i)
26
+ value = o.slice(i + 1, o.size)
27
+ value = value.strip unless value.nil?
28
+ if hash.has_key? key
29
+ hash[key] = [hash[key], value].flatten
30
+ else
31
+ hash[key] = value
32
+ end
33
+
34
+ hash
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,20 @@
1
+ module Typhoeus
2
+ class Service
3
+ def initialize(host, port)
4
+ @host = host
5
+ @port = port
6
+ end
7
+
8
+ def get(resource, params)
9
+ end
10
+
11
+ def put(resource, params)
12
+ end
13
+
14
+ def post(resource, params)
15
+ end
16
+
17
+ def delete(resource, params)
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+ # File: script/console
3
+ irb = RUBY_PLATFORM =~ /(:?mswin|mingw)/ ? 'irb.bat' : 'irb'
4
+
5
+ libs = " -r irb/completion"
6
+ # Perhaps use a console_lib to store any extra methods I may want available in the cosole
7
+ # libs << " -r #{File.dirname(__FILE__) + '/../lib/console_lib/console_logger.rb'}"
8
+ libs << " -r #{File.dirname(__FILE__) + '/../lib/chimera.rb'}"
9
+ puts "Loading chimera gem"
10
+ exec "#{irb} #{libs} --simple-prompt"
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+ APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
3
+
4
+ begin
5
+ require 'rubigen'
6
+ rescue LoadError
7
+ require 'rubygems'
8
+ require 'rubigen'
9
+ end
10
+ require 'rubigen/scripts/destroy'
11
+
12
+ ARGV.shift if ['--help', '-h'].include?(ARGV[0])
13
+ RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit]
14
+ RubiGen::Scripts::Destroy.new.run(ARGV)
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+ APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
3
+
4
+ begin
5
+ require 'rubigen'
6
+ rescue LoadError
7
+ require 'rubygems'
8
+ require 'rubigen'
9
+ end
10
+ require 'rubigen/scripts/generate'
11
+
12
+ ARGV.shift if ['--help', '-h'].include?(ARGV[0])
13
+ RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit]
14
+ RubiGen::Scripts::Generate.new.run(ARGV)
@@ -0,0 +1,49 @@
1
+ class User < Chimera::Base
2
+ use_config :default # this is implied even if not here
3
+
4
+ attribute :name
5
+ attribute :age
6
+ attribute :occupation
7
+ attribute :interests
8
+ attribute :home_coordinate # [37.2,122.1]
9
+ attribute :ssn
10
+ attribute :updated_at
11
+ attribute :favorite_car, :model, :class => :car
12
+
13
+ # User.find_with_index(:home_coordinate, {:coordinate => [37.2,122.1], :steps => 5})
14
+ index :home_coordinate, :type => :geo, :step_size => 0.05
15
+
16
+ # User.find_with_index(:occupation, { :q => "developer", :type => :intersect } ) # fuzzy search. :intersect or :union
17
+ index :occupation, :type => :search
18
+
19
+ # User.find_with_index(:ssn, "12345") # exact search, enforces unique constraint
20
+ index :ssn, :type => :unique
21
+
22
+ # User.find_with_index(:name, "Ben") # like :search but exact
23
+ index :name, :type => :find
24
+
25
+ association :friends, :user
26
+ association :cars, :car
27
+
28
+ redis_object :num_logins, :counter
29
+
30
+ validates_presence_of :name
31
+ end
32
+
33
+ class Car < Chimera::Base
34
+ attribute :color
35
+ attribute :make
36
+ attribute :model
37
+ attribute :year
38
+ attribute :mileage
39
+ attribute :comments
40
+ attribute :sku
41
+ attribute :curr_location
42
+
43
+ index :year, :type => :find
44
+ index :comments, :type => :search
45
+ index :sku, :type => :unique
46
+ index :curr_location, :type => :geo, :step_size => 0.05
47
+
48
+ validates_presence_of :make, :model, :year
49
+ end
@@ -0,0 +1,238 @@
1
+ require File.dirname(__FILE__) + '/test_helper.rb'
2
+
3
+ class TestChimera < Test::Unit::TestCase
4
+ def setup
5
+ Car.each { |c| c.destroy }
6
+ Car.connection(:redis).flush_all
7
+ end
8
+
9
+ def test_geo_indexes
10
+ c = Car.new
11
+ c.make = "Porsche"
12
+ c.model = "911"
13
+ c.year = 2010
14
+ c.sku = 1000
15
+ c.id = Car.new_uuid
16
+ c.curr_location = [37.12122, 121.43392]
17
+ assert c.save
18
+
19
+ c2 = Car.new
20
+ c2.make = "Toyota"
21
+ c2.model = "Hilux"
22
+ c2.year = 2010
23
+ c2.sku = 1001
24
+ c2.curr_location = [37.12222, 121.43792]
25
+ c2.id = Car.new_uuid
26
+ assert c2.save
27
+
28
+ found = Car.find_with_index(:curr_location, {:coordinate => [37.12222, 121.43792], :steps => 5})
29
+ assert_equal [c,c2].sort, found.sort
30
+
31
+ c2.curr_location = [38.0, 122.0]
32
+ assert c2.save
33
+
34
+ found = Car.find_with_index(:curr_location, {:coordinate => [37.12222, 121.43792], :steps => 5})
35
+ assert_equal [c].sort, found.sort
36
+
37
+ found = Car.find_with_index(:curr_location, {:coordinate => [38.0-0.05, 122.0+0.05], :steps => 5})
38
+ assert_equal [c2].sort, found.sort
39
+ end
40
+
41
+ def test_search_indexes
42
+ c = Car.new
43
+ c.make = "Porsche"
44
+ c.model = "911"
45
+ c.year = 2010
46
+ c.sku = 1000
47
+ c.comments = "cat dog chicken dolphin whale panther"
48
+ c.id = Car.new_uuid
49
+ assert c.save
50
+
51
+ c2 = Car.new
52
+ c2.make = "Porsche"
53
+ c2.model = "911"
54
+ c2.year = 2010
55
+ c2.sku = 1001
56
+ c2.comments = "cat dog chicken"
57
+ c2.id = Car.new_uuid
58
+ assert c2.save
59
+
60
+ c3 = Car.new
61
+ c3.make = "Porsche"
62
+ c3.model = "911"
63
+ c3.year = 2010
64
+ c3.sku = 1002
65
+ c3.comments = "dog chicken dolphin whale"
66
+ c3.id = Car.new_uuid
67
+ assert c3.save
68
+
69
+ assert_equal [c,c2,c3].sort, Car.find_with_index(:comments, "dog").sort
70
+ assert_equal [c,c2].sort, Car.find_with_index(:comments, "cat").sort
71
+ assert_equal [c,c2].sort, Car.find_with_index(:comments, "cat").sort
72
+
73
+ assert_equal [c,c2,c3].sort, Car.find_with_index(:comments, {:q => "dog dolphin", :type => :union}).sort
74
+ assert_equal [c,c3].sort, Car.find_with_index(:comments, {:q => "dog dolphin", :type => :intersect}).sort
75
+ end
76
+
77
+ def test_indexes
78
+ c = Car.new
79
+ c.make = "Nissan"
80
+ c.model = "RX7"
81
+ c.year = 2010
82
+ c.sku = 1001
83
+ c.comments = "really fast car. it's purple too!"
84
+ c.id = Car.new_uuid
85
+ assert c.save
86
+
87
+ assert !c.new?
88
+
89
+ assert_equal [c], Car.find_with_index(:comments, "fast")
90
+ assert_equal [c], Car.find_with_index(:comments, "purple")
91
+ assert_equal [], Car.find_with_index(:comments, "blue")
92
+
93
+ assert_equal [c], Car.find_with_index(:year, 2010)
94
+ assert_equal [c], Car.find_with_index(:sku, 1001)
95
+
96
+ c2 = Car.new
97
+ c2.make = "Honda"
98
+ c2.model = "Accord"
99
+ c2.year = 2010
100
+ c2.sku = 1001
101
+ c2.id = Car.new_uuid
102
+ assert_raise(Chimera::Error::UniqueConstraintViolation) { c2.save }
103
+ c2.sku = 1002
104
+ assert c2.save
105
+
106
+ c3 = Car.new
107
+ c3.make = "Honda"
108
+ c3.model = "Civic"
109
+ c3.year = 2010
110
+ c3.sku = 1003
111
+ c3.id = Car.new_uuid
112
+ assert c3.save
113
+
114
+ assert_equal 3, Car.find_with_index(:year, 2010).size
115
+ assert Car.find_with_index(:year, 2010).include?(c)
116
+ assert Car.find_with_index(:year, 2010).include?(c2)
117
+ assert Car.find_with_index(:year, 2010).include?(c3)
118
+
119
+ count = 0
120
+ Car.find_with_index(:all) { |car| count += 1 }
121
+ assert_equal 3, count
122
+
123
+ count = 0
124
+ Car.each { |car| count += 1 }
125
+ assert_equal 3, count
126
+
127
+ c2.destroy
128
+
129
+ count = 0
130
+ Car.find_with_index(:all) { |car| count += 1 }
131
+ assert_equal 2, count
132
+
133
+ count = 0
134
+ Car.each { |car| count += 1 }
135
+ assert_equal 2, count
136
+ end
137
+
138
+ def test_associations
139
+ u = User.new
140
+ u.id = User.new_uuid
141
+ u.name = "Ben"
142
+ assert u.save
143
+
144
+ assert_equal 0, u.friends.size
145
+
146
+ chris = User.new
147
+ chris.id = User.new_uuid
148
+ chris.name = "Chris"
149
+ assert chris.save
150
+
151
+ assert_equal 0, u.friends.size
152
+ u.friends << chris
153
+ assert_equal 1, u.friends.size
154
+ chris.destroy
155
+ assert_equal 0, u.friends.size
156
+
157
+ c = Car.new
158
+ c.make = "Nissan"
159
+ c.model = "RX7"
160
+ c.year = 2010
161
+ c.sku = 1001
162
+ c.comments = "really fast car. it's purple too!"
163
+ c.id = Car.new_uuid
164
+ assert c.save
165
+
166
+ assert_equal 0, u.cars.size
167
+ u.cars << c
168
+ assert_equal 1, u.cars.size
169
+ assert_equal [c], u.cars.all
170
+ assert_equal 1, c.association_memberships.all_associations.size
171
+ u.cars.remove(c)
172
+ assert_equal 0, c.association_memberships.all_associations.size
173
+ end
174
+
175
+ def test_model_attribute
176
+ u = User.new
177
+ u.id = User.new_uuid
178
+ u.name = "Ben"
179
+ assert u.save
180
+ assert_nil u.favorite_car
181
+
182
+ c = Car.new
183
+ c.make = "Nissan"
184
+ c.model = "RX7"
185
+ c.year = 2010
186
+ c.sku = 1001
187
+ c.comments = "really fast car. it's purple too!"
188
+ c.id = Car.new_uuid
189
+ assert c.save
190
+
191
+ u.favorite_car = c
192
+ assert u.save
193
+ assert_equal c, u.favorite_car
194
+ u = User.find(u.id)
195
+ assert_equal c, u.favorite_car
196
+ u.favorite_car = nil
197
+ assert u.save
198
+ assert_nil u.favorite_car
199
+
200
+ u.favorite_car = c
201
+ assert u.save
202
+ assert_equal c, u.favorite_car
203
+ c.destroy
204
+ assert_equal c, u.favorite_car
205
+ u = User.find(u.id)
206
+ assert_nil u.favorite_car
207
+ end
208
+
209
+ def test_redis_objects
210
+ u = User.new
211
+ u.id = User.new_uuid
212
+ u.name = "Ben"
213
+ assert u.save
214
+
215
+ assert_equal false, User.connection(:redis).exists(u.num_logins.key)
216
+ assert_equal 0, u.num_logins.count
217
+ u.num_logins.incr
218
+ assert_equal 1, u.num_logins.count
219
+ assert_equal true, User.connection(:redis).exists(u.num_logins.key)
220
+ u.num_logins.incr_by 10
221
+ assert_equal 11, u.num_logins.count
222
+
223
+ u.destroy
224
+
225
+ assert_equal false, User.connection(:redis).exists(u.num_logins.key)
226
+ end
227
+
228
+ def test_rich_attributes
229
+ u = User.new
230
+ u.id = User.new_uuid
231
+ u.updated_at = Time.now.utc
232
+ assert u.updated_at.is_a?(Time)
233
+ u.name = "ben"
234
+ assert u.save
235
+ u = User.find(u.id)
236
+ assert u.updated_at.is_a?(Time)
237
+ end
238
+ end