zeng 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 (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