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.
- data/CHANGE +3 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +89 -0
- data/Rakefile +37 -0
- data/TODOLIST +5 -0
- data/init.rb +4 -0
- data/lib/cute_kv/adapters/light_cloud.rb +22 -0
- data/lib/cute_kv/adapters/tokyo_cabinet.rb +33 -0
- data/lib/cute_kv/adapters/tokyo_tyrant.rb +22 -0
- data/lib/cute_kv/associations.rb +285 -0
- data/lib/cute_kv/connector.rb +27 -0
- data/lib/cute_kv/document.rb +328 -0
- data/lib/cute_kv/ext/string.rb +34 -0
- data/lib/cute_kv/ext/symbol.rb +9 -0
- data/lib/cute_kv/indexer.rb +102 -0
- data/lib/cute_kv/serialization.rb +95 -0
- data/lib/cute_kv/serializers/json_serializer.rb +75 -0
- data/lib/cute_kv/serializers/xml_serializer.rb +325 -0
- data/lib/cute_kv/timestamp.rb +56 -0
- data/lib/cute_kv/validations.rb +68 -0
- data/lib/cutekv.rb +17 -0
- data/spec/asso.yml +23 -0
- data/spec/asso_sin_plural.yml +36 -0
- data/spec/case/associations_test.rb +313 -0
- data/spec/case/document_docking_test.rb +103 -0
- data/spec/case/document_test.rb +184 -0
- data/spec/case/indexer_test.rb +40 -0
- data/spec/case/serialization_test.rb +78 -0
- data/spec/case/sin_plu_dic_test.rb +29 -0
- data/spec/case/symmetry_test.rb +80 -0
- data/spec/case/timestamp_test.rb +65 -0
- data/spec/case/validations_test.rb +74 -0
- data/spec/helper.rb +29 -0
- data/spec/light_cloud.yml +9 -0
- data/spec/model/Account.rb +5 -0
- data/spec/model/Book.rb +6 -0
- data/spec/model/Friend.rb +4 -0
- data/spec/model/Icon.rb +5 -0
- data/spec/model/Project.rb +5 -0
- data/spec/model/Topic.rb +26 -0
- data/spec/model/User.rb +6 -0
- data/tags +322 -0
- 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,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
|