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,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
+