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,325 @@
1
+ module CuteKV#:nodoc:
2
+ module Serialization
3
+ # Builds an XML document to represent the model. Some configuration is
4
+ # available through +options+. However more complicated cases should
5
+ # override ActiveObject::Base#to_xml.
6
+ #
7
+ # By default the generated XML document will include the processing
8
+ # instruction and all the object's attributes. For example:
9
+ #
10
+ # <?xml version="1.0" encoding="UTF-8"?>
11
+ # <topic>
12
+ # <title>The First Topic</title>
13
+ # <author-name>David</author-name>
14
+ # <id type="integer">1</id>
15
+ # <approved type="boolean">false</approved>
16
+ # <replies-count type="integer">0</replies-count>
17
+ # <bonus-time type="datetime">2000-01-01T08:28:00+12:00</bonus-time>
18
+ # <written-on type="datetime">2003-07-16T09:28:00+1200</written-on>
19
+ # <content>Have a nice day</content>
20
+ # <author-email-address>david@loudthinking.com</author-email-address>
21
+ # <parent-id></parent-id>
22
+ # <last-read type="date">2004-04-15</last-read>
23
+ # </topic>
24
+ #
25
+ # This behavior can be controlled with <tt>:only</tt>, <tt>:except</tt>,
26
+ # <tt>:skip_instruct</tt>, <tt>:skip_types</tt> and <tt>:dasherize</tt>.
27
+ # The <tt>:only</tt> and <tt>:except</tt> options are the same as for the
28
+ # +attributes+ method. The default is to dasherize all column names, but you
29
+ # can disable this setting <tt>:dasherize</tt> to +false+. To not have the
30
+ # column type included in the XML output set <tt>:skip_types</tt> to +true+.
31
+ #
32
+ # For instance:
33
+ #
34
+ # topic.to_xml(:skip_instruct => true, :except => [ :id, :bonus_time, :written_on, :replies_count ])
35
+ #
36
+ # <topic>
37
+ # <title>The First Topic</title>
38
+ # <author-name>David</author-name>
39
+ # <approved type="boolean">false</approved>
40
+ # <content>Have a nice day</content>
41
+ # <author-email-address>david@loudthinking.com</author-email-address>
42
+ # <parent-id></parent-id>
43
+ # <last-read type="date">2004-04-15</last-read>
44
+ # </topic>
45
+ #
46
+ # To include first level associations use <tt>:include</tt>:
47
+ #
48
+ # firm.to_xml :include => [ :account, :clients ]
49
+ #
50
+ # <?xml version="1.0" encoding="UTF-8"?>
51
+ # <firm>
52
+ # <id type="integer">1</id>
53
+ # <rating type="integer">1</rating>
54
+ # <name>37signals</name>
55
+ # <clients type="array">
56
+ # <client>
57
+ # <rating type="integer">1</rating>
58
+ # <name>Summit</name>
59
+ # </client>
60
+ # <client>
61
+ # <rating type="integer">1</rating>
62
+ # <name>Microsoft</name>
63
+ # </client>
64
+ # </clients>
65
+ # <account>
66
+ # <id type="integer">1</id>
67
+ # <credit-limit type="integer">50</credit-limit>
68
+ # </account>
69
+ # </firm>
70
+ #
71
+ # To include deeper levels of associations pass a hash like this:
72
+ #
73
+ # firm.to_xml :include => {:account => {}, :clients => {:include => :address}}
74
+ # <?xml version="1.0" encoding="UTF-8"?>
75
+ # <firm>
76
+ # <id type="integer">1</id>
77
+ # <rating type="integer">1</rating>
78
+ # <name>37signals</name>
79
+ # <clients type="array">
80
+ # <client>
81
+ # <rating type="integer">1</rating>
82
+ # <name>Summit</name>
83
+ # <address>
84
+ # ...
85
+ # </address>
86
+ # </client>
87
+ # <client>
88
+ # <rating type="integer">1</rating>
89
+ # <name>Microsoft</name>
90
+ # <address>
91
+ # ...
92
+ # </address>
93
+ # </client>
94
+ # </clients>
95
+ # <account>
96
+ # <id type="integer">1</id>
97
+ # <credit-limit type="integer">50</credit-limit>
98
+ # </account>
99
+ # </firm>
100
+ #
101
+ # To include any methods on the model being called use <tt>:methods</tt>:
102
+ #
103
+ # firm.to_xml :methods => [ :calculated_earnings, :real_earnings ]
104
+ #
105
+ # <firm>
106
+ # # ... normal attributes as shown above ...
107
+ # <calculated-earnings>100000000000000000</calculated-earnings>
108
+ # <real-earnings>5</real-earnings>
109
+ # </firm>
110
+ #
111
+ # To call any additional Procs use <tt>:procs</tt>. The Procs are passed a
112
+ # modified version of the options hash that was given to +to_xml+:
113
+ #
114
+ # proc = Proc.new { |options| options[:builder].tag!('abc', 'def') }
115
+ # firm.to_xml :procs => [ proc ]
116
+ #
117
+ # <firm>
118
+ # # ... normal attributes as shown above ...
119
+ # <abc>def</abc>
120
+ # </firm>
121
+ #
122
+ # Alternatively, you can yield the builder object as part of the +to_xml+ call:
123
+ #
124
+ # firm.to_xml do |xml|
125
+ # xml.creator do
126
+ # xml.first_name "David"
127
+ # xml.last_name "Heinemeier Hansson"
128
+ # end
129
+ # end
130
+ #
131
+ # <firm>
132
+ # # ... normal attributes as shown above ...
133
+ # <creator>
134
+ # <first_name>David</first_name>
135
+ # <last_name>Heinemeier Hansson</last_name>
136
+ # </creator>
137
+ # </firm>
138
+ #
139
+ # As noted above, you may override +to_xml+ in your ActiveObject::Base
140
+ # subclasses to have complete control about what's generated. The general
141
+ # form of doing this is:
142
+ #
143
+ # class IHaveMyOwnXML < ActiveObject::Base
144
+ # def to_xml(options = {})
145
+ # options[:indent] ||= 2
146
+ # xml = options[:builder] ||= Builder::XmlMarkup.new(:indent => options[:indent])
147
+ # xml.instruct! unless options[:skip_instruct]
148
+ # xml.level_one do
149
+ # xml.tag!(:second_level, 'content')
150
+ # end
151
+ # end
152
+ # end
153
+ def to_xml(options = {}, &block)
154
+ serializer = XmlSerializer.new(self, options)
155
+ block_given? ? serializer.to_s(&block) : serializer.to_s
156
+ end
157
+
158
+ end
159
+
160
+ class XmlSerializer < CuteKV::Serialization::Serializer #:nodoc:
161
+ def builder
162
+ @builder ||= begin
163
+ options[:indent] ||= 2
164
+ builder = options[:builder] ||= Builder::XmlMarkup.new(:indent => options[:indent])
165
+
166
+ unless options[:skip_instruct]
167
+ builder.instruct!
168
+ options[:skip_instruct] = true
169
+ end
170
+
171
+ builder
172
+ end
173
+ end
174
+
175
+ def root
176
+ root = (options[:root] || @object.class.to_s.underscore).to_s
177
+ dasherize? ? root.dasherize : root
178
+ end
179
+
180
+ def dasherize?
181
+ !options.has_key?(:dasherize) || options[:dasherize]
182
+ end
183
+
184
+ def serializable_attributes
185
+ serializable_attribute_names.collect { |name| Attribute.new(name.to_s, @object) }
186
+ end
187
+
188
+ def serializable_method_attributes
189
+ Array(options[:methods]).inject([]) do |method_attributes, name|
190
+ method_attributes << MethodAttribute.new(name.to_s, @object) if @object.respond_to?(name.to_s)
191
+ method_attributes
192
+ end
193
+ end
194
+
195
+ def add_attributes
196
+ (serializable_attributes + serializable_method_attributes).each do |attribute|
197
+ add_tag(attribute)
198
+ end
199
+ end
200
+
201
+ def add_procs
202
+ if procs = options.delete(:procs)
203
+ [ *procs ].each do |proc|
204
+ proc.call(options)
205
+ end
206
+ end
207
+ end
208
+
209
+ def add_tag(attribute)
210
+ builder.tag!(
211
+ dasherize? ? attribute.name.dasherize : attribute.name,
212
+ attribute.value.to_s,
213
+ attribute.decorations(!options[:skip_types])
214
+ )
215
+ end
216
+
217
+ def add_associations(association, objects, opts)
218
+ if objects.is_a?(Enumerable)
219
+ tag = association.to_s
220
+ tag = tag.dasherize if dasherize?
221
+ if objects.empty?
222
+ builder.tag!(tag, :type => :array)
223
+ else
224
+ builder.tag!(tag, :type => :array) do
225
+ association_name = association.to_s.singularize
226
+ objects.each do |object|
227
+ object.to_xml opts.merge(
228
+ :root => association_name,
229
+ :type => (object.class.to_s.underscore == association_name ? nil : object.class.name)
230
+ )
231
+ end
232
+ end
233
+ end
234
+ else
235
+ if object = @object.send(association)
236
+ object.to_xml(opts.merge(:root => association))
237
+ end
238
+ end
239
+ end
240
+
241
+ def serialize
242
+ args = [root]
243
+ if options[:namespace]
244
+ args << {:xmlns=>options[:namespace]}
245
+ end
246
+
247
+ if options[:type]
248
+ args << {:type=>options[:type]}
249
+ end
250
+
251
+ builder.tag!(*args) do
252
+ add_attributes
253
+ procs = options.delete(:procs)
254
+ add_includes { |association, objects, opts| add_associations(association, objects, opts) }
255
+ options[:procs] = procs
256
+ add_procs
257
+ yield builder if block_given?
258
+ end
259
+ end
260
+
261
+ class Attribute #:nodoc:
262
+ attr_reader :name, :value, :type
263
+
264
+ def initialize(name, object)
265
+ @name, @object = name, object
266
+
267
+ @type = compute_type
268
+ @value = compute_value
269
+ end
270
+
271
+ # There is a significant speed improvement if the value
272
+ # does not need to be escaped, as <tt>tag!</tt> escapes all values
273
+ # to ensure that valid XML is generated. For known binary
274
+ # values, it is at least an order of magnitude faster to
275
+ # Base64 encode binary values and directly put them in the
276
+ # output XML than to pass the original value or the Base64
277
+ # encoded value to the <tt>tag!</tt> method. It definitely makes
278
+ # no sense to Base64 encode the value and then give it to
279
+ # <tt>tag!</tt>, since that just adds additional overhead.
280
+ def needs_encoding?
281
+ ![ :binary, :date, :datetime, :boolean, :float, :integer ].include?(type)
282
+ end
283
+
284
+ def decorations(include_types = true)
285
+ decorations = {}
286
+
287
+ if type == :binary
288
+ decorations[:encoding] = 'base64'
289
+ end
290
+
291
+ if include_types && type != :string
292
+ decorations[:type] = type
293
+ end
294
+
295
+ if value.nil?
296
+ decorations[:nil] = true
297
+ end
298
+
299
+ decorations
300
+ end
301
+
302
+ protected
303
+ def compute_type
304
+ type = @object.send(name).class
305
+ end
306
+
307
+ def compute_value
308
+ value = @object.send(name)
309
+
310
+ if formatter = Hash::XML_FORMATTING[type.to_s]
311
+ value ? formatter.call(value) : nil
312
+ else
313
+ value
314
+ end
315
+ end
316
+ end
317
+
318
+ class MethodAttribute < Attribute #:nodoc:
319
+ protected
320
+ def compute_type
321
+ Hash::XML_TYPE_NAMES[@object.send(name).class.name] || :string
322
+ end
323
+ end
324
+ end
325
+ end
@@ -0,0 +1,56 @@
1
+ module CuteKV
2
+ # CuteKV automatically timestamps create and update operations if the table has fields
3
+ # named created_at/created_on or updated_at/updated_on.
4
+ #
5
+ # Timestamping can be turned off by setting
6
+ # <tt>ActiveRecord::Base.record_timestamps = false</tt>
7
+ #
8
+ # Timestamps are in the local timezone by default but you can use UTC by setting
9
+ # <tt>CuteKV::Timestampes.default_timezone == :utc</tt>
10
+ module Timestamp
11
+
12
+ def self.included(base) #:nodoc:
13
+ base.extend ClassMethods
14
+ base.alias_method_chain :save, :timestamps
15
+ end
16
+
17
+
18
+
19
+ module ClassMethods
20
+ def default_timezone
21
+ @timezone = Timestamp::default_timezone
22
+ @timezone.to_sym
23
+ end
24
+
25
+ def add_timestamps
26
+ assign(:created_at, :updated_at) unless already_has_timestamp?
27
+ end
28
+
29
+ def already_has_timestamp?
30
+ attrs = self.assigned_attributes
31
+ attrs.include?(:created_at) && attrs.include?(:updated_at)
32
+ end
33
+
34
+ end
35
+
36
+ private
37
+
38
+ def save_with_timestamps #:nodoc:
39
+ t = self.class.default_timezone == :utc ? DateTime.now.utc : DateTime.now
40
+ self.created_at ||= t if self.respond_to?(:created_at)
41
+ self.updated_at = t if self.respond_to?(:updated_at)
42
+ save_without_timestamps
43
+ end
44
+
45
+
46
+ class << self
47
+
48
+ def default_timezone
49
+ @default_tz ||= :utc
50
+ end
51
+
52
+ end
53
+
54
+ end
55
+
56
+ end
@@ -0,0 +1,68 @@
1
+ module CuteKV
2
+
3
+ module Validations
4
+
5
+ def self.included(base)
6
+ base.extend ClassMethods
7
+ base.send :include, InstanceMethods
8
+ base.class_eval { alias_method :validated, :save; alias_method :save, :validating }
9
+ end
10
+
11
+ class Error
12
+
13
+ def initialize
14
+ @body = Hash.new {|h,k| h[k] = []}
15
+ end
16
+
17
+ def add(attr, mesg)
18
+ attr = attr.to_s
19
+ @body[attr] << mesg unless @body[attr].include?(mesg)
20
+ end
21
+
22
+ def on(attr)
23
+ attr = attr.to_s
24
+ @body[attr].size == 1 ? @body[attr].first : @body[attr]
25
+ end
26
+
27
+ def empty?
28
+ @body.values.flatten.empty?
29
+ end
30
+
31
+ end
32
+
33
+ module InstanceMethods
34
+
35
+ def validating
36
+ self.class.validations.each {|v| self.send v}
37
+ valid? ? validated : nil
38
+ end
39
+
40
+ def errors
41
+ @errors ||= Error.new
42
+ end
43
+
44
+ def valid?
45
+ self.errors.empty?
46
+ end
47
+
48
+ def errors_message_on(attr)
49
+ self.errors.on(attr)
50
+ end
51
+
52
+ end
53
+
54
+ module ClassMethods
55
+
56
+ def validate(validate_process)
57
+ (@validations ||=[]) << validate_process
58
+ end
59
+
60
+ def validations
61
+ (@validations ||=[]).dup
62
+ end
63
+
64
+ end
65
+
66
+ end
67
+
68
+ end
data/lib/cutekv.rb ADDED
@@ -0,0 +1,17 @@
1
+ $:.unshift(File.dirname(__FILE__)) unless
2
+ $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
3
+
4
+ require 'rubygems'
5
+ require 'lightcloud'
6
+ require 'active_support'
7
+ require 'cute_kv/ext/string'
8
+ require 'cute_kv/ext/symbol'
9
+ require 'cute_kv/document'
10
+ require 'cute_kv/validations'
11
+ require 'cute_kv/connector'
12
+ require 'cute_kv/associations'
13
+ require 'cute_kv/indexer'
14
+ require 'cute_kv/serialization'
15
+ require 'cute_kv/connector'
16
+ require 'cute_kv/timestamp'
17
+
data/spec/asso.yml ADDED
@@ -0,0 +1,23 @@
1
+ -
2
+ Book:
3
+ owner
4
+ User:
5
+ books
6
+ -
7
+ User:
8
+ project
9
+ Project:
10
+ master
11
+ -
12
+ User:
13
+ projects
14
+ Project:
15
+ members
16
+ -
17
+ User:
18
+ icon
19
+ Icon:
20
+ user
21
+
22
+
23
+
@@ -0,0 +1,36 @@
1
+ -
2
+ Book:
3
+ owner
4
+ User:
5
+ book
6
+ plural:
7
+ book
8
+ singular:
9
+ owner
10
+ -
11
+ User:
12
+ projects
13
+ Project:
14
+ members
15
+ plural:
16
+ projects
17
+ members
18
+ -
19
+ User:
20
+ icons
21
+ Icon:
22
+ users
23
+ singular:
24
+ icons
25
+ users
26
+ -
27
+ User:
28
+ husband
29
+ wife
30
+ -
31
+ User:
32
+ friends
33
+
34
+
35
+
36
+