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
@@ -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