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
@@ -0,0 +1,328 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'yaml'
3
+ require 'uuid'
4
+ require 'json'
5
+ require 'json/add/core'
6
+
7
+ module CuteKV
8
+ class CuteKVError < StandardError;end
9
+ class ObjectNotSaved < CuteKVError; end
10
+
11
+ # CuteKV's built-in attribute id is the <t>key</t> to access <t>value</t>, and id is formated by uuid.
12
+ # ==
13
+ # CuteKV provide a Document module to persistence class's attributes, when a class include Document module,
14
+ # then it can <t>assign</t> attributes needed to persistence.
15
+ #
16
+ # class User
17
+ # include CuteKV::Document
18
+ # assign :name,:age=>25,:gender=>"male"
19
+ # end
20
+ #
21
+ # User have three persistence attributes: "name", "age"(default value is 25) and "gender"(default male)
22
+ # @jim = User.new(:name=>"jim")
23
+ # @jim.name #=> "jim"
24
+ # @jim.age #=> 25
25
+ # @jim.gender #=> "male"
26
+ #
27
+ # Every object has a default +id+, if you want to set primary key, you can use <t>primary_key</t> to set up.
28
+ #
29
+ # class Account < ActiveObject::Base
30
+ # include CuteKV::Document
31
+ # assign :name,:email,:encrypt_password
32
+ # primary_key :email
33
+ # end
34
+ #
35
+ # == New
36
+ # Use hash params to create a new object, example:
37
+ # user = User.new(:name => "David", :occupation => "Code Artist")
38
+ # user.name # => "David"
39
+ #
40
+ # you can initialize object by using block
41
+ # user = User.new {|u| u.name = "David"; u.occupation = "Code Artist"}
42
+ #
43
+ # Also,you can first create a bare object then assign values to it
44
+ # user = User.new
45
+ # user.name = "David"
46
+ # user.occupation = "Code Artist"
47
+ #
48
+ # == Create
49
+ #
50
+ # == Update
51
+ #
52
+ # == Destroy
53
+ #
54
+ # ==== params
55
+ #
56
+ # * +object+ is what you want to destroy
57
+ #
58
+ # ==== example
59
+ # jim = User.create(:name=>"jim")
60
+ # User.Destroy(jim)
61
+ # if Account.destroy(jim) will return nil, because jim is not a instance object of Account.
62
+
63
+
64
+ module Document
65
+
66
+ def self.included(base)
67
+ base.extend ClassMethods
68
+ # select serializers, exmaple "json", ",marshal", default is "json"
69
+ base.select_serializer(:json)
70
+ base.send :include, InstanceMethods
71
+ base.send :include, Serialization
72
+ base.send :include, Timestamp
73
+ add_client(base) unless clients.include?(base)
74
+ end
75
+
76
+ module InstanceMethods
77
+ attr_reader :id
78
+
79
+ def initialize(attributes={}, new_id=true)
80
+ if new_id
81
+ @id = UUID.new.generate
82
+ assigned_attrs = self.class.read_assigned_attributes_with_default_values
83
+ attributes.each_key {|key| assigned_attrs.delete(key)}
84
+ attributes.merge!(assigned_attrs)
85
+ end
86
+ attributes.each {|attr, value| self.send "#{attr}=", value if self.respond_to? "#{attr}=" }
87
+ yield self if block_given?
88
+ end
89
+
90
+ # we protect class's backend from instance objects
91
+ def save
92
+ self.class.save(self)
93
+ end
94
+
95
+ def serialized_attributes
96
+ seri_attrs = assigned_attributes.inject({}){|h,attr|
97
+ h[attr] = self.send attr if self.respond_to? attr
98
+ h
99
+ }
100
+ #
101
+ #don't use :id, or you will get @id=nil when you use marshal to serialize object's attributes
102
+ seri_attrs["id"] = @id
103
+ self.class.serialize seri_attrs
104
+ end
105
+
106
+ def assigned_attributes
107
+ self.class.assigned_attributes
108
+ end
109
+
110
+ # update object's attributes
111
+ # <tt>attributes</tt>are the attributes needed to be updated
112
+ def update(attributes={})
113
+ self.class.update(self, attributes)
114
+ end
115
+
116
+
117
+
118
+ # reload object from database
119
+ def reload
120
+ self.class.reload(self)
121
+ end
122
+
123
+ # destroy self from database
124
+ def destroy
125
+ self.class.destroy(self)
126
+ end
127
+
128
+ def assigned_attributes_values
129
+ self.assigned_attributes.inject({}){|h,attr| h[attr] = self.send attr; h }
130
+ end
131
+
132
+ end
133
+
134
+ module ClassMethods
135
+
136
+ # Assigning attributs needed to persistence
137
+ # Example:
138
+ # class User
139
+ # include CuteKV::Document
140
+ # assign :name, :country=>"China", :gender=>"male"
141
+ # end
142
+ def assign(*attributes)
143
+ attrs = attributes.inject({}){ |h,attr|
144
+ if attr.is_a? Hash
145
+ attr.each {|k, v| attr_accessor k; h[k.to_sym] = v }
146
+ else
147
+ attr_accessor attr
148
+ h[attr.to_sym] = nil
149
+ end
150
+ h
151
+ }
152
+ write_assigned_attributes_with_default_values(attrs)
153
+ end
154
+
155
+ def write_assigned_attributes_with_default_values(attributes={})
156
+ (@assigned_attributes_with_default_values ||={}).merge!(attributes)
157
+ end
158
+
159
+ def read_assigned_attributes_with_default_values
160
+ @assigned_attributes_with_default_values.dup
161
+ end
162
+
163
+ def assigned_attributes
164
+ @assigned_attributes_with_default_values.keys
165
+ end
166
+
167
+ def backend
168
+ @backend
169
+ end
170
+
171
+
172
+ #empty database
173
+ def clear
174
+ @backend.clear
175
+ end
176
+
177
+
178
+ # Configure CuteKV's Back-end database,now support LightCloud/TokyoTyrant/TokyoCabinet.
179
+ # +adapter+ specify what database to use
180
+ # :TC => TokyoCabinet (few to use :TC in our practice projects)
181
+ # :TT => TokyoTyrant
182
+ # :LC => LightCloud
183
+ #
184
+ # +config+ is the config infos about back-end
185
+ # when +adapter+ is specified to :TT, +config+ coulde be a String or Hash
186
+ # String:
187
+ # User.backend_configure :TT, "127.0.0.1:1985"
188
+ # or Hash:
189
+ # User.backend_configure :TT, :host=>'127.0.0.1', :port=>1985
190
+ #
191
+ #
192
+ #Back-end database,now we support tokyotyrant, lightcloud
193
+ def backend_configure(adapter, config)
194
+ @backend = Connector.new(adapter,config)
195
+ end
196
+
197
+
198
+ # 返回当前对象的数据库和索引配置信息
199
+ def backend_configurations
200
+ @backend.infos
201
+ end
202
+
203
+
204
+ #
205
+ # find object by it's +id+
206
+ # ==== Example
207
+ # User.find('aaron@nonobo.com') return an User's instance object
208
+ # if User's backend has this id or return nil
209
+ def find(id)
210
+ return if id.nil?
211
+ raw_value = @backend.get(id)
212
+ return if raw_value.nil?
213
+ value = deserialize raw_value
214
+ active(value)
215
+ end
216
+
217
+ # Active sleeped value so we'll get a live object :-)
218
+ # It's should not generate a new id, because this value has included a id
219
+ def active(value, new_id=false)
220
+ id = value.delete("id")
221
+ new(value, new_id) {|obj| obj.instance_variable_set(:@id, id)}
222
+ end
223
+
224
+ def all
225
+ end
226
+
227
+ # Create one or more objects and save them to database
228
+ # return objects you have created
229
+ # parameters +attributes+ is Hash or hash Array
230
+ #
231
+ # ==== Example
232
+ # # create single object
233
+ # User.create(:first_name => 'Jamie')
234
+ #
235
+ # # create more objects
236
+ # User.create([{ :first_name => 'Jamie' }, { :first_name => 'Jeremy' }])
237
+ #
238
+ # # create an object, and assign values to attributes through block
239
+ # User.create(:first_name => 'Jamie') do |u|
240
+ # u.is_admin = false
241
+ # end
242
+ #
243
+ def create(attributes = {}, &block)
244
+ object = new(attributes)
245
+ yield(object) if block_given?
246
+ object.save
247
+ object
248
+ end
249
+
250
+ def save(object)
251
+ object.is_a?(self) ? @backend.put(object.id, object.serialized_attributes) : nil
252
+ end
253
+
254
+ def update(object, attributes={})
255
+ object.is_a?(self) ? attributes.each{|key,value|
256
+ object.send("#{key}=",value) if object.respond_to? "#{key}="
257
+ } : nil
258
+ end
259
+
260
+ def reload(object)
261
+ return unless object.is_a?(self)
262
+ raw_value = @backend.get(object.id)
263
+ return if raw_value.nil?
264
+ value = deserialize raw_value
265
+ update(object, value)
266
+ end
267
+
268
+
269
+ # destroy object who is an instance of Actors # and execute all callback and filt actions
270
+ # ==== Example
271
+ # aaron = User.create(:name=>"aaron")
272
+ # User.destroy(aaron)
273
+ def destroy(object)
274
+ object.is_a?(self) ? @backend.delete(object.id) : nil
275
+ end
276
+
277
+ Serializers ={
278
+ :json => {:ser=> lambda{|value| JSON.generate(value)},
279
+ :desr=>lambda{|raw| JSON.parse(raw)},
280
+ :type=>"json"
281
+ },
282
+ :marshal => {:ser=> lambda{|value| Marshal.dump(value)},
283
+ :desr=>lambda{|raw| Marshal.load(raw)},
284
+ :type => "marshal"
285
+ }
286
+ }
287
+
288
+ def select_serializer(type=:json)
289
+ @serializer = Serializers[type]
290
+ end
291
+
292
+ def serializer_type
293
+ @serializer[:type]
294
+ end
295
+
296
+ def serialize(value)
297
+ @serializer[:ser].call(value)
298
+ end
299
+
300
+ def deserialize(raw_value)
301
+ @serializer[:desr].call(raw_value)
302
+ end
303
+
304
+ end
305
+
306
+ class << self
307
+ def backend_configure(klass,adapter, host_port )
308
+ end
309
+
310
+ # docking external mod, so that expanding Document's functions
311
+ # and class who has included Document will hold the exteranl mod's methods.
312
+ def docking(mod)
313
+ @clients.each {|client| client.send :include, mod }
314
+ end
315
+
316
+ def add_client(client)
317
+ (@clients ||= []) << client if client.is_a? Class
318
+ end
319
+
320
+ def clients
321
+ (@clients ||= []).dup
322
+ end
323
+
324
+ end
325
+
326
+ end
327
+
328
+ end
@@ -0,0 +1,34 @@
1
+ class String
2
+
3
+ def plural?
4
+ (Dic::PLU_WORDS.include?(self) || self.pluralize == self) && !Dic::SIN_WORDS.include?(self)
5
+ end
6
+
7
+ def singular?
8
+ (Dic::SIN_WORDS.include?(self) || self.singularize == self) && !Dic::PLU_WORDS.include?(self)
9
+ end
10
+
11
+ end
12
+
13
+ module WordAct
14
+ def add(words)
15
+ if words.is_a? Array
16
+ words.each {|w| self.add(w.to_s)}
17
+ else
18
+ self << words.to_s
19
+ Dic::Mirror[self].delete(words.to_s)
20
+ self.uniq!
21
+ end
22
+ end
23
+
24
+ def hash
25
+ self.object_id
26
+ end
27
+ end
28
+
29
+ module Dic
30
+ (SIN_WORDS = []).extend(WordAct)
31
+ (PLU_WORDS = []).extend(WordAct)
32
+ Mirror = {SIN_WORDS=>PLU_WORDS, PLU_WORDS=>SIN_WORDS}
33
+ end
34
+
@@ -0,0 +1,9 @@
1
+ class Symbol
2
+ def plural?
3
+ self.to_s.plural?
4
+ end
5
+
6
+ def singular?
7
+ self.to_s.singular?
8
+ end
9
+ end
@@ -0,0 +1,102 @@
1
+ module CuteKV
2
+ module Indexer
3
+ Map = Class.new
4
+ Collection = Class.new(Array)
5
+
6
+ def self.included(base)
7
+ base.extend ClassMethods
8
+ base.send :include, InstanceMethods
9
+ end
10
+
11
+ module InstanceMethods
12
+ attr_reader :key, :values
13
+ attr_accessor :index_attrs
14
+
15
+
16
+ def initialize(key, value)
17
+ @key = key
18
+ @values = JSON.parse value
19
+ end
20
+
21
+ def save
22
+ self.class.save(self)
23
+ end
24
+
25
+ def objects
26
+ map = self
27
+ index_attrs = self.index_attrs
28
+ Collection.class_eval {
29
+ define_method(:<<){|obj|
30
+ old = map.values.assoc(obj.id)
31
+ map.values.delete(old)
32
+ index = index_attrs.inject({}){
33
+ |h, attr| h[attr] = obj.send(attr) if obj.respond_to?(attr)
34
+ h
35
+ }
36
+ index.merge!(:id=>obj.id)
37
+ map.values << [obj.id,index]
38
+ map.save
39
+
40
+ }
41
+ }
42
+ values = Collection.new.replace(self.values)
43
+ end
44
+
45
+ end
46
+
47
+ module ClassMethods
48
+ def connect(klass)
49
+ @backend = klass.backend
50
+ end
51
+
52
+ def find(key)
53
+ new(key, @backend[key]) if @backend[key]
54
+ end
55
+
56
+ def create(key, values=[])
57
+ value = JSON.generate values
58
+ @backend[key] = value
59
+ new(key, value)
60
+ end
61
+
62
+ def save(map)
63
+ @backend[map.key] = JSON.generate map.values
64
+ end
65
+
66
+ def find_or_create(key)
67
+ find(key) || create(key)
68
+ end
69
+
70
+ end
71
+
72
+
73
+ class << self
74
+
75
+ def map(class_attrs={})
76
+ c = class_attrs.keys[0]
77
+ attrs = class_attrs.values.flatten
78
+ key = "#{c}-index-#{attrs.join('-')}"
79
+ index_map = Map.send(:include, self)
80
+ index_map.connect(c)
81
+ klass = class << c; self; end
82
+ klass.instance_eval {
83
+ define_method(:indexes){
84
+ map = index_map.find_or_create(key)
85
+ map.index_attrs = attrs
86
+ map.objects
87
+ }
88
+ attrs.each {|attr|
89
+ define_method("find_all_by_#{attr}"){|value|
90
+ c.indexes.map {|index|
91
+ c.find(index[-1]['id']) if index[-1][attr.to_s] == value
92
+ }.compact
93
+ }
94
+ }
95
+ }
96
+ true
97
+ end
98
+
99
+ end
100
+
101
+ end
102
+ end
@@ -0,0 +1,95 @@
1
+ module CuteKV#:nodoc:
2
+ module Serialization
3
+ class Serializer #:nodoc:
4
+ attr_reader :options
5
+
6
+ def initialize(object, options = {})
7
+ @object, @options = object, options.dup
8
+ end
9
+
10
+ # To replicate the behavior in ActiveObject#attributes,
11
+ # <tt>:except</tt> takes precedence over <tt>:only</tt>. If <tt>:only</tt> is not set
12
+ # for a N level model but is set for the N+1 level models,
13
+ # then because <tt>:except</tt> is set to a default value, the second
14
+ # level model can have both <tt>:except</tt> and <tt>:only</tt> set. So if
15
+ # <tt>:only</tt> is set, always delete <tt>:except</tt>.
16
+ def serializable_attribute_names
17
+ attribute_names = @object.assigned_attributes.collect {|n| n.to_s} << "id"
18
+
19
+ if options[:only]
20
+ options.delete(:except)
21
+ attribute_names = attribute_names & Array(options[:only]).collect { |n| n.to_s }
22
+ else
23
+ options[:except] = Array(options[:except])
24
+ attribute_names = attribute_names - options[:except].collect { |n| n.to_s }
25
+ end
26
+
27
+ attribute_names
28
+ end
29
+
30
+ def serializable_method_names
31
+ Array(options[:methods]).inject([]) do |method_attributes, name|
32
+ method_attributes << name if @object.respond_to?(name.to_s)
33
+ method_attributes
34
+ end
35
+ end
36
+
37
+ def serializable_names
38
+ serializable_attribute_names + serializable_method_names
39
+ end
40
+
41
+ # Add associations specified via the <tt>:includes</tt> option.
42
+ # Expects a block that takes as arguments:
43
+ # +association+ - name of the association
44
+ # +objects+ - the association object(s) to be serialized
45
+ # +opts+ - options for the association objects
46
+ def add_includes(&block)
47
+ if include_associations = options.delete(:include)
48
+ base_only_or_except = { :except => options[:except],
49
+ :only => options[:only] }
50
+
51
+ include_has_options = include_associations.is_a?(Hash)
52
+ associations = include_has_options ? include_associations.keys : Array(include_associations)
53
+
54
+ for association in associations
55
+ objects = @object.send association
56
+
57
+ unless objects.nil?
58
+ association_options = include_has_options ? include_associations[association] : base_only_or_except
59
+ opts = options.merge(association_options)
60
+ yield(association, objects, opts)
61
+ end
62
+ end
63
+
64
+ options[:include] = include_associations
65
+ end
66
+ end
67
+
68
+ def serializable_object
69
+ returning(serializable_object = {}) do
70
+ serializable_names.each { |name| serializable_object[name] = @object.send(name) }
71
+
72
+ add_includes do |association, objects, opts|
73
+ if objects.is_a?(Enumerable)
74
+ serializable_object[association] = objects.collect { |r| self.class.new(r, opts).serializable_object }
75
+ else
76
+ serializable_object[association] = self.class.new(objects, opts).serializable_object
77
+ end
78
+
79
+ end
80
+ end
81
+ end
82
+
83
+ def serialize
84
+ # overwrite to implement
85
+ end
86
+
87
+ def to_s(&block)
88
+ serialize(&block)
89
+ end
90
+ end
91
+ end
92
+ end
93
+
94
+ require 'cute_kv/serializers/xml_serializer'
95
+ require 'cute_kv/serializers/json_serializer'
@@ -0,0 +1,75 @@
1
+ module CuteKV#:nodoc:
2
+ module Serialization
3
+ def self.included(base)
4
+ base.cattr_accessor :include_root_in_json, :instance_writer => false
5
+ base.extend ClassMethods
6
+ end
7
+
8
+ # Returns a JSON string representing the model. Some configuration is
9
+ # available through +options+.
10
+ #
11
+ # Without any +options+, the returned JSON string will include all
12
+ # the model's attributes. For example:
13
+ #
14
+ # konata = User.find(1)
15
+ # konata.to_json
16
+ # # => {"id": 1, "name": "Konata Izumi", "age": 16,
17
+ # "created_at": "2006/08/01", "awesome": true}
18
+ #
19
+ # The <tt>:only</tt> and <tt>:except</tt> options can be used to limit the attributes
20
+ # included, and work similar to the +attributes+ method. For example:
21
+ #
22
+ # konata.to_json(:only => [ :id, :name ])
23
+ # # => {"id": 1, "name": "Konata Izumi"}
24
+ #
25
+ # konata.to_json(:except => [ :id, :created_at, :age ])
26
+ # # => {"name": "Konata Izumi", "awesome": true}
27
+ #
28
+ # To include any methods on the model, use <tt>:methods</tt>.
29
+ #
30
+ # konata.to_json(:methods => :permalink)
31
+ # # => {"id": 1, "name": "Konata Izumi", "age": 16,
32
+ # "created_at": "2006/08/01", "awesome": true,
33
+ # "permalink": "1-konata-izumi"}
34
+ #
35
+ # To include associations, use <tt>:include</tt>.
36
+ #
37
+ # konata.to_json(:include => :posts)
38
+ # # => {"id": 1, "name": "Konata Izumi", "age": 16,
39
+ # "created_at": "2006/08/01", "awesome": true,
40
+ # "posts": [{"id": 1, "author_id": 1, "title": "Welcome to the weblog"},
41
+ # {"id": 2, author_id: 1, "title": "So I was thinking"}]}
42
+ #
43
+ # 2nd level and higher order associations work as well:
44
+ #
45
+ # konata.to_json(:include => { :posts => {
46
+ # :include => { :comments => {
47
+ # :only => :body } },
48
+ # :only => :title } })
49
+ # # => {"id": 1, "name": "Konata Izumi", "age": 16,
50
+ # "created_at": "2006/08/01", "awesome": true,
51
+ # "posts": [{"comments": [{"body": "1st post!"}, {"body": "Second!"}],
52
+ # "title": "Welcome to the weblog"},
53
+ # {"comments": [{"body": "Don't think too hard"}],
54
+ # "title": "So I was thinking"}]}
55
+ def to_json(options = {})
56
+ if include_root_in_json
57
+ "{#{self.class.json_class_name}: #{JsonSerializer.new(self, options).to_s}}"
58
+ else
59
+ JsonSerializer.new(self, options).to_s
60
+ end
61
+ end
62
+
63
+ class JsonSerializer < CuteKV::Serialization::Serializer #:nodoc:
64
+ def serialize
65
+ serializable_object.to_json
66
+ end
67
+ end
68
+
69
+ module ClassMethods
70
+ def json_class_name
71
+ @json_class_name ||= name.demodulize.underscore.inspect
72
+ end
73
+ end
74
+ end
75
+ end