zeng 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. data/CHANGE +3 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.rdoc +89 -0
  4. data/Rakefile +37 -0
  5. data/TODOLIST +5 -0
  6. data/init.rb +4 -0
  7. data/lib/cute_kv/adapters/light_cloud.rb +22 -0
  8. data/lib/cute_kv/adapters/tokyo_cabinet.rb +33 -0
  9. data/lib/cute_kv/adapters/tokyo_tyrant.rb +22 -0
  10. data/lib/cute_kv/associations.rb +285 -0
  11. data/lib/cute_kv/connector.rb +27 -0
  12. data/lib/cute_kv/document.rb +328 -0
  13. data/lib/cute_kv/ext/string.rb +34 -0
  14. data/lib/cute_kv/ext/symbol.rb +9 -0
  15. data/lib/cute_kv/indexer.rb +102 -0
  16. data/lib/cute_kv/serialization.rb +95 -0
  17. data/lib/cute_kv/serializers/json_serializer.rb +75 -0
  18. data/lib/cute_kv/serializers/xml_serializer.rb +325 -0
  19. data/lib/cute_kv/timestamp.rb +56 -0
  20. data/lib/cute_kv/validations.rb +68 -0
  21. data/lib/cutekv.rb +17 -0
  22. data/spec/asso.yml +23 -0
  23. data/spec/asso_sin_plural.yml +36 -0
  24. data/spec/case/associations_test.rb +313 -0
  25. data/spec/case/document_docking_test.rb +103 -0
  26. data/spec/case/document_test.rb +184 -0
  27. data/spec/case/indexer_test.rb +40 -0
  28. data/spec/case/serialization_test.rb +78 -0
  29. data/spec/case/sin_plu_dic_test.rb +29 -0
  30. data/spec/case/symmetry_test.rb +80 -0
  31. data/spec/case/timestamp_test.rb +65 -0
  32. data/spec/case/validations_test.rb +74 -0
  33. data/spec/helper.rb +29 -0
  34. data/spec/light_cloud.yml +9 -0
  35. data/spec/model/Account.rb +5 -0
  36. data/spec/model/Book.rb +6 -0
  37. data/spec/model/Friend.rb +4 -0
  38. data/spec/model/Icon.rb +5 -0
  39. data/spec/model/Project.rb +5 -0
  40. data/spec/model/Topic.rb +26 -0
  41. data/spec/model/User.rb +6 -0
  42. data/tags +322 -0
  43. metadata +124 -0
data/CHANGE ADDED
@@ -0,0 +1,3 @@
1
+ 2010-1-20 add docking method for CuteKV::Document, enhance it's scalability
2
+ 2010-1-20 add CuteKV::Timestamp module supporting timestamp function
3
+ 2010-1-24 add CuteKV::Validations module supporting validate
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Aaron
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,89 @@
1
+ = CuteKV -- based at Ruby for object-key/value map
2
+
3
+
4
+ == Main features
5
+ * Independent Object Storage
6
+
7
+ Through backend_configure to appoint storage location
8
+ class User < ActiveObject::Base
9
+ backend_configure :TT,"127.0.0.1:1987"
10
+ end
11
+
12
+ * Customize define persistent properties
13
+ you can assign persitent properties by <t>assign</t> method,and set default value for
14
+ each property.
15
+
16
+ class User
17
+ include CuteKV::Document
18
+ backend_configure :TT,"127.0.0.1:1987"
19
+ assign :name,:email, :gender=>'male', :age=>25
20
+ end
21
+
22
+ ==Object's associations
23
+
24
+ Using CuteKV::associations module, we can description associations between objects
25
+
26
+
27
+ class User
28
+ include CuteKV::Document
29
+ backend_configure :TT,"127.0.0.1:1987"
30
+ assign :name,:email, :gender=>'male', :age=>25
31
+ end
32
+
33
+ CuteKV::associations::map(User=>:icon, Icon=>:user) #=>user has one icon and icon belongs to one user
34
+ CuteKV::associations::map(User=>:friends) #=>user has many friends
35
+
36
+ class Icon < ActiveObject::Base
37
+ include CuteKV::Document
38
+ backend_configure :TT,"127.0.0.1:1987"
39
+ assign :content
40
+ end
41
+
42
+ ==Validations
43
+ now CuteKV support validations by a simple method <t>validate</t> from CuteKV::Validations module
44
+ I just keep it simple or i will improve it to approach activerecord's validations
45
+ class User
46
+ include CuteKV::Document
47
+ incude CuteKV::Validations
48
+ backend_configure :TT,"127.0.0.1:1987"
49
+ assign :name,:email, :gender=>'male', :age=>25
50
+ validate :name_presence_valid
51
+
52
+ def name_presence_valid
53
+ errors.add(:name, "name should not blank") if self.name.blank?
54
+ end
55
+ end
56
+
57
+ @jim = User.create()
58
+ @jim.save #=>nil
59
+ User.find(@jim.id) #=>nil
60
+ @jim.errors_message_on(:name) #=>"name should not blank"
61
+
62
+
63
+ ==Index
64
+ class User
65
+ include CuteKV::Document
66
+ backend_configure :TT,"127.0.0.1:1987"
67
+ assign :name,:email, :gender=>'male', :age=>25
68
+ end
69
+ @jim = User.create(:name=>"jim", :email=>"jim@nonobo.com")
70
+ @aaron = User.create(:name=>"aaron", :email=>"aaron@nonobo.com")
71
+ @jack= User.create(:name=>"jack", :email=>"jack@nonbo.com")
72
+ @lucy = User.create(:name=>"lucy", :email=>"lucy@nonobo.com")
73
+ Useing CuteKV::Indexer module, you can build index for object, just like:
74
+ CuteKV::Indexer::map(User=>[:name, :email, :age])
75
+ then,
76
+ User.indexes << @jim
77
+ User.indexes << @aaron
78
+ User.indexes << @lucy
79
+ User.find_all_by_name("jim") #=>@jim
80
+ User.find_all_by_age(25) #=>@jim, @aaron, @lucy, @jack
81
+
82
+ == Supoort multiple database
83
+ CuteKV using adapter to connect database.now backend support:TokyoCabinet/TokyoTyrant/LightCloud。
84
+
85
+
86
+ == Using in rails
87
+ in environment.rb, you will add
88
+ require 'cutekv'
89
+
data/Rakefile ADDED
@@ -0,0 +1,37 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rake/rdoctask'
4
+
5
+ task :default=>:test
6
+
7
+ task :test do
8
+ Dir['spec/case/**/*'].each {|test|
9
+ sh "ruby #{test}" if test =~ /(_test\.rb)$/
10
+ }
11
+ end
12
+
13
+ desc "start test for given file"
14
+ task :test_file, [:file] do |t, args|
15
+ sh "ruby spec/case/#{args.file}_test.rb"
16
+ end
17
+
18
+ desc "创建gemspec文件"
19
+ task :gemspec do
20
+ spec = Gem::Specification.new do |s|
21
+ s.name = %{zeng}
22
+ s.version = '0.0.1'
23
+ s.summary = 'A chinese fishing tool'
24
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
25
+
26
+ s.email = ["kayak.jiang@gmail.com"]
27
+ s.authors = "Guimin Jiang"
28
+ s.files = Dir["./**/*"].delete_if {|path| path =~ /.gem$/}
29
+ s.require_paths = ["lib"]
30
+ s.required_ruby_version = Gem::Requirement.new(">= 1.8.6")
31
+ s.rubygems_version = %q{1.3.4}
32
+ s.add_dependency(%q<ffi>)
33
+ end
34
+
35
+ File.open("zeng.gemspec", "w") {|f| f << spec.to_ruby }
36
+ end
37
+
data/TODOLIST ADDED
@@ -0,0 +1,5 @@
1
+ like activerecord i will add
2
+ 1. validate
3
+
4
+
5
+
data/init.rb ADDED
@@ -0,0 +1,4 @@
1
+ $:.unshift(File.dirname(__FILE__)) unless
2
+ $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
3
+
4
+ require 'cutekv'
@@ -0,0 +1,22 @@
1
+ # 访问LightCloud的加载模块
2
+ require 'lightcloud'
3
+ module CuteKV
4
+ module Adapters
5
+ module TokyoCloud
6
+ def put(key, value)
7
+ @db.set(key, value)
8
+ end
9
+
10
+ def get(key)
11
+ @db.get(key)
12
+ end
13
+
14
+ private
15
+ def establish(conf)
16
+ conf = YAML.load_file(conf) if conf.is_a?(String)
17
+ lookup_nodes, storage_nodes = LightCloud.generate_nodes(conf)
18
+ @db = LightCloud.new(lookup_nodes, storage_nodes)
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,33 @@
1
+ # -*- coding: utf-8 -*-
2
+ # 访问TokyoCabinet的加载模块
3
+ require 'rufus/tokyo'
4
+ module CuteKV
5
+ module Adapters
6
+ module TokyoCabinet
7
+ def put(key, value)
8
+ @db[key] = value
9
+ end
10
+
11
+ def get(key)
12
+ @db[key]
13
+ end
14
+
15
+ def delete(key)
16
+ @db.delete(key)
17
+ end
18
+
19
+ def infos
20
+ {:adapter=>@adapter, :host=>@host, :port=>@port}
21
+ end
22
+
23
+ def method_missing(method, *args)
24
+ @db.send method, *args
25
+ end
26
+
27
+ private
28
+ def establish(conf)
29
+ @db = Rufus::Tokyo::Cabinet.new(conf)
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,22 @@
1
+ # 访问TokyoTyrant的加载模块
2
+ require 'rufus/tokyo'
3
+ require 'cute_kv/adapters/tokyo_cabinet'
4
+ module CuteKV
5
+ module Adapters
6
+ module TokyoTyrant
7
+ include CuteKV::Adapters::TokyoCabinet
8
+ private
9
+ def establish(conf)
10
+ if conf.is_a? Hash
11
+ @host = conf[:host] || conf["host"]
12
+ @port = (conf[:port] || conf["port"]).to_i
13
+ else
14
+ conf = conf.split(':')
15
+ @host = conf[0]
16
+ @port = conf[1].to_i
17
+ end
18
+ @db = Rufus::Tokyo::Tyrant.new(@host, @port)
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,285 @@
1
+ module CuteKV
2
+ module Associations
3
+
4
+ Map = Class.new
5
+ Collection = Class.new(Array) {
6
+ def to_json(options={})
7
+ "[#{self.map{|c| c.to_json(options)}.join(',')}]"
8
+ end
9
+ }
10
+
11
+ class Symmetry
12
+
13
+ def initialize(asso={})
14
+ @asso_string = parse(asso)
15
+ @assos = @asso_string.split("#")
16
+ end
17
+
18
+ def mirror(object)
19
+ m_o = @assos[@assos.size - 1 - @assos.index(object.to_s)]
20
+ m_o = m_o.constantize if object.is_a? Class
21
+ m_o = m_o.to_sym if object.is_a? Symbol
22
+ m_o
23
+ end
24
+
25
+ def each
26
+ asso = [[@assos[0].constantize, @assos[1].to_sym ],[@assos[-1].constantize, @assos[-2].to_sym]].uniq
27
+ asso.each {|a| yield a[0], a[-1]}
28
+ end
29
+
30
+ private
31
+ def parse(asso={})
32
+ keys = asso.keys
33
+ values = asso.values.flatten
34
+ "#{keys[0]}##{values[0]}##{values[-1]}##{keys[-1]}"
35
+ end
36
+
37
+ end
38
+
39
+ def self.included(base)
40
+ base.send :include, InstanceMethods
41
+ base.extend ClassMethods
42
+ end
43
+
44
+ module InstanceMethods
45
+
46
+ def initialize(key, ids)
47
+ @key_infos = key.split('#')
48
+ @klass = @key_infos[-1].constantize
49
+ @key = key
50
+ @ids = deserialize ids
51
+ end
52
+
53
+ def ids
54
+ @ids
55
+ end
56
+
57
+ def relex(object)
58
+ r_k = @key_infos[0]
59
+ r_v = @key_infos[1]
60
+ k = @key_infos[-1]
61
+ v = @key_infos[-2]
62
+ key = "#{k}##{v}##{object.id}#relations##{r_v}##{r_k}"
63
+ map = self.class.find(key) || self.class.create(key, [])
64
+ end
65
+
66
+
67
+ def objects
68
+ map = self
69
+ #it is very strange! when i use 'id', i can't use the method 'remove' correctly, so i use '_id'
70
+ _id = @key_infos[2]
71
+ _klass = @klass
72
+ _rv = @key_infos[1]
73
+ objs = Collection.new
74
+ objs.replace self.ids.collect {|id| @klass.find(id)}.compact
75
+ self.ids = objs.map(&:id) unless objs.size == self.ids.size
76
+ Collection.class_eval {
77
+ define_method(:<<){|object|
78
+ return unless object.is_a? _klass
79
+ if _rv.singular?
80
+ map.send(:ids=, [object.id])
81
+ else
82
+ map.ids << object.id
83
+ end
84
+ rel_map = map.relex(object)
85
+ rel_map.ids << _id
86
+ map.save
87
+ rel_map.save
88
+ }
89
+
90
+ define_method(:remove) {|object|
91
+ return unless object.is_a? _klass
92
+ map.ids.delete(object.id)
93
+ rel_map = map.relex(object)
94
+ rel_map.ids.delete(_id)
95
+ map.save
96
+ rel_map.save
97
+ }
98
+
99
+
100
+ }
101
+ objs
102
+ end
103
+
104
+ def save
105
+ self.class.backend[@key] = serialize @ids
106
+ end
107
+
108
+
109
+ private
110
+ def ids=(ids)
111
+ return unless ids.is_a? Array
112
+ @ids = ids
113
+ end
114
+
115
+ def serialize(ids)
116
+ self.class.send :serialize, ids
117
+ end
118
+
119
+ def deserialize(raw_ids)
120
+ self.class.send :deserialize, raw_ids
121
+ end
122
+
123
+ end
124
+
125
+
126
+ module ClassMethods
127
+
128
+ def connect(class_obj)
129
+ @backend = class_obj.backend
130
+ @class_obj = class_obj
131
+ end
132
+
133
+ def find(key)
134
+ new(key, @backend[key]) if @backend[key]
135
+ end
136
+
137
+ def create(key,ids=[])
138
+ ids = serialize ids
139
+ @backend[key] = ids
140
+ new(key, ids)
141
+ end
142
+
143
+ def backend
144
+ @backend
145
+ end
146
+
147
+ def find_or_create(key)
148
+ find(key) || create(key, [])
149
+ end
150
+
151
+ def gen_key(k, v, object, r_v, r_k)
152
+ key = "#{k}##{v}##{object.id}#relations##{r_v}##{r_k}"
153
+ end
154
+
155
+ def draw(k, v, object, r_v, r_k)
156
+ key = gen_key(k, v, object, r_v, r_k)
157
+ find_or_create(key)
158
+ end
159
+
160
+ private
161
+
162
+ def serialize(ids)
163
+ @class_obj.serialize(ids)
164
+ end
165
+
166
+ def deserialize(raw_ids)
167
+ @class_obj.deserialize(raw_ids)
168
+ end
169
+
170
+ end
171
+
172
+ class << self
173
+ # Although relations between real word's objects are complex, we abstract all relations to three
174
+ # type relations: # <tt>one_to_one</tt>, # <tt>one_to_many</tt>, # <tt>many_to_many</tt>
175
+ #
176
+ # == One_To_One
177
+ # class User < ActiveObject::Base
178
+ # assign :name, :gender=>"male"
179
+ # end
180
+ #
181
+ # class Icon < ActiveObject::Base
182
+ # assign :path
183
+ # end
184
+ # @aaron = User.create(:name=>"aaron")
185
+ # @icon = Icon.create(:path=>"/tmp/aaron.jpg")
186
+ #
187
+ # Associations::map(User=>:icon, Icon=>:user)
188
+ # User will add a instance method :icon
189
+ # Icon will add a instance method :user
190
+ # @aaron.icon = @icon
191
+ # aaron = User.find(@aaron.id)
192
+ # aaron.icon.path #=>"/tmp/aaron.jpg"
193
+ # icon = Icon.find(@icon.id)
194
+ # icon.user.name #=>"aaron"
195
+ #
196
+ # Associations::map(User=>[:wife, :husband])
197
+ # @rita = User.create(:name=>"rita", :gender=>'female')
198
+ # @aaron.wife = @rita
199
+ # @aaron.wife #=>@rita
200
+ # @rita.husband #=>@aaron
201
+ #
202
+ # == One_To_Many
203
+ # Associations::map(User=>:books, Book=>:owner)
204
+ # @book_ruby = Book.create(:name=>"Ruby")
205
+ # @book_java = Book.create(:name=>"Java")
206
+ #
207
+ # == Many_To_Many
208
+ # Associations::map(User=>:projects, Project=>:members)
209
+ # @aaron = User.create(:name=>"aaron")
210
+ # @nonobo = Project.create(:name=>"nonobo")
211
+ # @admin = Project.create(:name=>"admin")
212
+ # @aaron.prjects << @nonobo
213
+ # @aaron.prjects << @admin
214
+ # @aaron.projects #=>[@nonobo, @admin]
215
+ # @nonobo.members #=>[@aaron]
216
+ # @aaron.projects.remove(@nonbo)
217
+ # @nonbo.members #=> []
218
+ # @aaron.projects #=> [@admin]
219
+ #
220
+ #
221
+ def map(asso)
222
+ if asso.is_a? String
223
+ #load classes's associations from yml file
224
+ assos = YAML::load(IO.read(asso))
225
+ assos.each {|asso| map(constantize_asso_keys(asso)) }
226
+ else
227
+ singus = Array(asso.delete(:singular)).compact
228
+ Dic::SIN_WORDS.add(singus) unless singus.empty?
229
+ pluras = Array(asso.delete(:plural)).compact
230
+ Dic::PLU_WORDS.add(pluras) unless pluras.empty?
231
+ symmetry = Symmetry.new(asso)
232
+ symmetry.each {|k,v|
233
+ r_k = symmetry.mirror(k)
234
+ r_v = symmetry.mirror(v)
235
+ asso_map = Map.send(:include, self)
236
+ asso_map.connect(k)
237
+ k.class_eval {
238
+ define_method(v) {
239
+ map = asso_map.draw(k,v,self,r_v,r_k)
240
+ if v.singular?
241
+ obj = map.objects.last
242
+ return if obj.nil?
243
+ def obj.remove(object)
244
+ Collection.new.replace([self]).remove(object)
245
+ end
246
+ obj
247
+ else
248
+ map.objects
249
+ end
250
+ }
251
+
252
+ define_method("#{v}=") {|object|
253
+ return if object.nil?
254
+ map = asso_map.draw(k,v,self,r_v,r_k)
255
+ vo = self.send(v)
256
+ vo.send(r_v).remove(self) if vo
257
+ rvo = object.send(r_v)
258
+ rvo.send(v).remove(object) if r_v.singular? && rvo
259
+ map.objects << object
260
+ } if v.singular?
261
+
262
+ }
263
+ }
264
+ end
265
+
266
+ end
267
+
268
+ protected
269
+
270
+ def constantize_asso_keys(asso)
271
+ h = {}
272
+ plurs = asso.delete("plural").split(" ") if asso["plural"]
273
+ sins = asso.delete("singular").split(" ") if asso["singular"]
274
+ h[:plural] = plurs if plurs
275
+ h[:singular] = sins if sins
276
+ asso.each {|k,v| h[k.constantize] = v.split(' ') }
277
+ h
278
+ end
279
+
280
+ end
281
+
282
+ end
283
+
284
+
285
+ end
@@ -0,0 +1,27 @@
1
+ module CuteKV
2
+ class Connector
3
+ def self.config(adapter)
4
+ case adapter.to_sym
5
+ when :TC
6
+ require 'cute_kv/adapters/tokyo_cabinet'
7
+ include CuteKV::Adapters::TokyoCabinet
8
+ when :TT
9
+ require 'cute_kv/adapters/tokyo_tyrant'
10
+ include CuteKV::Adapters::TokyoTyrant
11
+ when :LC
12
+ require 'cute_kv/adapters/light_cloud'
13
+ include CuteKV::Adapters::TokyoCloud
14
+ else
15
+ raise ConfigError,'没有指定数据库类型!'
16
+ end
17
+ end
18
+
19
+ def initialize(adapter, conf)
20
+ @adapter = adapter
21
+ self.class.config(adapter)
22
+ establish(conf)
23
+ end
24
+
25
+ end
26
+
27
+ end