zeng 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|