activeobject 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGE +10 -0
- data/Interface_desc +21 -0
- data/MIT-LICENSE +20 -0
- data/README +72 -0
- data/Rakefile.rb +9 -0
- data/active-object.gemspec +50 -0
- data/examples/account.rb +69 -0
- data/examples/data.tch +0 -0
- data/examples/light_cloud.yml +18 -0
- data/examples/test.rb +3 -0
- data/examples/user.rb +112 -0
- data/init.rb +4 -0
- data/lib/active-object.rb +23 -0
- data/lib/active_object/adapters/light_cloud.rb +40 -0
- data/lib/active_object/adapters/tokyo_cabinet.rb +48 -0
- data/lib/active_object/adapters/tokyo_tyrant.rb +14 -0
- data/lib/active_object/associations.rb +200 -0
- data/lib/active_object/base.rb +415 -0
- data/lib/active_object/callbacks.rb +180 -0
- data/lib/active_object/observer.rb +180 -0
- data/lib/active_object/serialization.rb +99 -0
- data/lib/active_object/serializers/json_serializer.rb +75 -0
- data/lib/active_object/serializers/xml_serializer.rb +325 -0
- data/lib/active_object/validations.rb +687 -0
- data/lib/active_support/callbacks.rb +303 -0
- data/lib/active_support/core_ext/array/access.rb +53 -0
- data/lib/active_support/core_ext/array/conversions.rb +183 -0
- data/lib/active_support/core_ext/array/extract_options.rb +20 -0
- data/lib/active_support/core_ext/array/grouping.rb +106 -0
- data/lib/active_support/core_ext/array/random_access.rb +12 -0
- data/lib/active_support/core_ext/array.rb +13 -0
- data/lib/active_support/core_ext/blank.rb +58 -0
- data/lib/active_support/core_ext/class/attribute_accessors.rb +54 -0
- data/lib/active_support/core_ext/class/inheritable_attributes.rb +140 -0
- data/lib/active_support/core_ext/class/removal.rb +50 -0
- data/lib/active_support/core_ext/class.rb +3 -0
- data/lib/active_support/core_ext/duplicable.rb +43 -0
- data/lib/active_support/core_ext/enumerable.rb +72 -0
- data/lib/active_support/core_ext/hash/conversions.rb +259 -0
- data/lib/active_support/core_ext/hash/keys.rb +52 -0
- data/lib/active_support/core_ext/hash.rb +8 -0
- data/lib/active_support/core_ext/module/aliasing.rb +74 -0
- data/lib/active_support/core_ext/module/attr_accessor_with_default.rb +31 -0
- data/lib/active_support/core_ext/module/attribute_accessors.rb +58 -0
- data/lib/active_support/core_ext/module.rb +16 -0
- data/lib/active_support/core_ext/object/conversions.rb +14 -0
- data/lib/active_support/core_ext/object/extending.rb +80 -0
- data/lib/active_support/core_ext/object/instance_variables.rb +74 -0
- data/lib/active_support/core_ext/object/metaclass.rb +13 -0
- data/lib/active_support/core_ext/object/misc.rb +43 -0
- data/lib/active_support/core_ext/object.rb +5 -0
- data/lib/active_support/core_ext/string/inflections.rb +167 -0
- data/lib/active_support/core_ext/string.rb +7 -0
- data/lib/active_support/core_ext.rb +4 -0
- data/lib/active_support/inflections.rb +55 -0
- data/lib/active_support/inflector.rb +348 -0
- data/lib/active_support/vendor/builder-2.1.2/blankslate.rb +113 -0
- data/lib/active_support/vendor/builder-2.1.2/builder/blankslate.rb +20 -0
- data/lib/active_support/vendor/builder-2.1.2/builder/css.rb +250 -0
- data/lib/active_support/vendor/builder-2.1.2/builder/xchar.rb +115 -0
- data/lib/active_support/vendor/builder-2.1.2/builder/xmlbase.rb +139 -0
- data/lib/active_support/vendor/builder-2.1.2/builder/xmlevents.rb +63 -0
- data/lib/active_support/vendor/builder-2.1.2/builder/xmlmarkup.rb +328 -0
- data/lib/active_support/vendor/builder-2.1.2/builder.rb +13 -0
- data/lib/active_support/vendor/xml-simple-1.0.11/xmlsimple.rb +1021 -0
- data/lib/active_support/vendor.rb +14 -0
- data/lib/active_support.rb +6 -0
- data/spec/case/association_test.rb +97 -0
- data/spec/case/base_test.rb +74 -0
- data/spec/case/callbacks_observers_test.rb +38 -0
- data/spec/case/callbacks_test.rb +424 -0
- data/spec/case/serialization_test.rb +87 -0
- data/spec/case/validations_test.rb +1482 -0
- data/spec/data.tch +0 -0
- data/spec/helper.rb +15 -0
- data/spec/light_cloud.yml +18 -0
- data/spec/model/account.rb +4 -0
- data/spec/model/topic.rb +26 -0
- data/spec/model/user.rb +8 -0
- metadata +173 -0
@@ -0,0 +1,180 @@
|
|
1
|
+
require 'observer'
|
2
|
+
|
3
|
+
module ActiveObject
|
4
|
+
# Callbacks是一个Ant Mapper对象生命周期内的钩子函数,允许你在对象状态改变的之前或之后触发相关规则.
|
5
|
+
|
6
|
+
# 当一个新对象的 <tt>Base#save</tt> 方法被调用时:
|
7
|
+
#
|
8
|
+
# * (-) <tt>save</tt>
|
9
|
+
# * (-) <tt>valid</tt>
|
10
|
+
# * (1) <tt>before_validation</tt>
|
11
|
+
# * (2) <tt>before_validation_on_create</tt>
|
12
|
+
# * (-) <tt>validate</tt>
|
13
|
+
# * (-) <tt>validate_on_create</tt>
|
14
|
+
# * (3) <tt>after_validation</tt>
|
15
|
+
# * (4) <tt>after_validation_on_create</tt>
|
16
|
+
# * (5) <tt>before_save</tt>
|
17
|
+
# * (6) <tt>before_create</tt>
|
18
|
+
# * (-) <tt>create</tt>
|
19
|
+
# * (7) <tt>after_create</tt>
|
20
|
+
# * (8) <tt>after_save</tt>
|
21
|
+
#
|
22
|
+
# 这里总共有8个回调.已经存在的记录调用<tt>Base#save</tt>时,除了<tt>_on_create</tt> 回调被<tt>_on_update</tt>取代外,其它都一样.
|
23
|
+
#
|
24
|
+
# 示例:
|
25
|
+
# class CreditCard < ActiveObject::Base
|
26
|
+
# # Strip everything but digits, so the user can specify "555 234 34" or
|
27
|
+
# # "5552-3434" or both will mean "55523434"
|
28
|
+
# def before_validation_on_create
|
29
|
+
# self.number = number.gsub(/[^0-9]/, "") if attribute_present?("number")
|
30
|
+
# end
|
31
|
+
# end
|
32
|
+
#
|
33
|
+
# class Subscription < ActiveObject::Base
|
34
|
+
# before_create :record_signup
|
35
|
+
#
|
36
|
+
# private
|
37
|
+
# def record_signup
|
38
|
+
# self.signed_up_on = Date.today
|
39
|
+
# end
|
40
|
+
# end
|
41
|
+
#
|
42
|
+
#
|
43
|
+
# == 继承回调序列
|
44
|
+
#
|
45
|
+
# 除了重写回调方法外,它也可以通过使用回调宏注册回调.宏能添加行为到回调序列,这样在多级继承时,能保留回调序列的完整。
|
46
|
+
# 示例:
|
47
|
+
#
|
48
|
+
# class Topic < ActiveObject::Base
|
49
|
+
# before_destroy :destroy_author
|
50
|
+
# end
|
51
|
+
#
|
52
|
+
# class Reply < Topic
|
53
|
+
# before_destroy :destroy_readers
|
54
|
+
# end
|
55
|
+
#
|
56
|
+
# 现在, 当 <tt>Topic#destroy</tt> 运行时只调用 +destroy_author+.当<tt>Reply#destroy</tt>运行时,将调用+destroy_author+ 和 # +destroy_readers+ 。
|
57
|
+
#
|
58
|
+
#
|
59
|
+
module Callbacks
|
60
|
+
CALLBACKS = %w(
|
61
|
+
after_find after_initialize before_save after_save before_create after_create before_update after_update before_validation
|
62
|
+
after_validation before_validation_on_create after_validation_on_create before_validation_on_update
|
63
|
+
after_validation_on_update before_destroy after_destroy
|
64
|
+
)
|
65
|
+
|
66
|
+
def self.included(base) #:nodoc:
|
67
|
+
base.extend Observable
|
68
|
+
|
69
|
+
[:initialize,:create_or_update, :valid?, :create, :update, :destroy].each do |method|
|
70
|
+
base.send :alias_method_chain, method, :callbacks
|
71
|
+
end
|
72
|
+
|
73
|
+
base.send :include, ActiveSupport::Callbacks
|
74
|
+
base.define_callbacks *CALLBACKS
|
75
|
+
end
|
76
|
+
|
77
|
+
# 在调用<tt>Base.new</tt>时,对象被初始化之后执行。
|
78
|
+
def after_initialize() end
|
79
|
+
|
80
|
+
def initialize_with_callbacks(*args)
|
81
|
+
result = initialize_without_callbacks(*args)
|
82
|
+
callback(:after_initialize)
|
83
|
+
result
|
84
|
+
end
|
85
|
+
|
86
|
+
# 在执行<tt>Base.save</tt>之前被调用 (不管是创建还是更新).
|
87
|
+
def before_save() end
|
88
|
+
|
89
|
+
# 在执行<tt>Base.save</tt>被调用(不管是创建还是更新).
|
90
|
+
#
|
91
|
+
# class Contact < ActiveObject::Base
|
92
|
+
# after_save { logger.info( 'New contact saved!' ) }
|
93
|
+
# end
|
94
|
+
def after_save() end
|
95
|
+
def create_or_update_with_callbacks #:nodoc:
|
96
|
+
return false if callback(:before_save) == false
|
97
|
+
result = create_or_update_without_callbacks
|
98
|
+
callback(:after_save)
|
99
|
+
result
|
100
|
+
end
|
101
|
+
private :create_or_update_with_callbacks
|
102
|
+
|
103
|
+
|
104
|
+
def before_create() end
|
105
|
+
|
106
|
+
def after_create() end
|
107
|
+
def create_with_callbacks #:nodoc:
|
108
|
+
return false if callback(:before_create) == false
|
109
|
+
result = create_without_callbacks
|
110
|
+
callback(:after_create)
|
111
|
+
result
|
112
|
+
end
|
113
|
+
private :create_with_callbacks
|
114
|
+
|
115
|
+
def before_update() end
|
116
|
+
|
117
|
+
def after_update() end
|
118
|
+
|
119
|
+
def update_with_callbacks(*args) #:nodoc:
|
120
|
+
return false if callback(:before_update) == false
|
121
|
+
result = update_without_callbacks(*args)
|
122
|
+
callback(:after_update)
|
123
|
+
result
|
124
|
+
end
|
125
|
+
private :update_with_callbacks
|
126
|
+
|
127
|
+
def before_validation() end
|
128
|
+
|
129
|
+
def after_validation() end
|
130
|
+
|
131
|
+
def before_validation_on_create() end
|
132
|
+
|
133
|
+
def after_validation_on_create() end
|
134
|
+
|
135
|
+
def before_validation_on_update() end
|
136
|
+
|
137
|
+
def after_validation_on_update() end
|
138
|
+
|
139
|
+
def valid_with_callbacks? #:nodoc:
|
140
|
+
return false if callback(:before_validation) == false
|
141
|
+
if new_record? then result = callback(:before_validation_on_create) else result = callback(:before_validation_on_update) end
|
142
|
+
return false if false == result
|
143
|
+
|
144
|
+
result = valid_without_callbacks?
|
145
|
+
|
146
|
+
callback(:after_validation)
|
147
|
+
if new_record? then callback(:after_validation_on_create) else callback(:after_validation_on_update) end
|
148
|
+
|
149
|
+
return result
|
150
|
+
end
|
151
|
+
|
152
|
+
def before_destroy() end
|
153
|
+
|
154
|
+
def after_destroy() end
|
155
|
+
def destroy_with_callbacks #:nodoc:
|
156
|
+
return false if callback(:before_destroy) == false
|
157
|
+
result = destroy_without_callbacks
|
158
|
+
callback(:after_destroy)
|
159
|
+
result
|
160
|
+
end
|
161
|
+
|
162
|
+
private
|
163
|
+
def callback(method)
|
164
|
+
result = run_callbacks(method) { |result, object| false == result }
|
165
|
+
|
166
|
+
if result != false #&& respond_to_without_attributes?(method)
|
167
|
+
result = send(method)
|
168
|
+
end
|
169
|
+
|
170
|
+
notify(method)
|
171
|
+
|
172
|
+
return result
|
173
|
+
end
|
174
|
+
|
175
|
+
def notify(method) #:nodoc:
|
176
|
+
self.class.changed
|
177
|
+
self.class.notify_observers(method, self)
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
@@ -0,0 +1,180 @@
|
|
1
|
+
require 'singleton'
|
2
|
+
require 'set'
|
3
|
+
|
4
|
+
module ActiveObject
|
5
|
+
module Observing # :nodoc:
|
6
|
+
def self.included(base)
|
7
|
+
base.extend ClassMethods
|
8
|
+
end
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
# 激活观察器. 示例:
|
12
|
+
#
|
13
|
+
# # 调用PersonObserver.instance
|
14
|
+
# ActiveObject::Base.observers = :person_observer
|
15
|
+
#
|
16
|
+
# # 调用Cacher.instance 和 GarbageCollector.instance
|
17
|
+
# ActiveObject::Base.observers = :cacher, :garbage_collector
|
18
|
+
#
|
19
|
+
# # 同上
|
20
|
+
# ActiveObject::Base.observers = Cacher, GarbageCollector
|
21
|
+
#
|
22
|
+
# Note: Setting this does not instantiate the observers yet. +instantiate_observers+ is
|
23
|
+
# called during startup, and before each development request.
|
24
|
+
def observers=(*observers)
|
25
|
+
@observers = observers.flatten
|
26
|
+
end
|
27
|
+
|
28
|
+
# 获得当前observers.
|
29
|
+
def observers
|
30
|
+
@observers ||= []
|
31
|
+
end
|
32
|
+
|
33
|
+
# 实例化全部Ant Mapper观察器.
|
34
|
+
def instantiate_observers
|
35
|
+
return if @observers.blank?
|
36
|
+
@observers.each do |observer|
|
37
|
+
if observer.respond_to?(:to_sym) # Symbol or String
|
38
|
+
observer.to_s.camelize.constantize.instance
|
39
|
+
elsif observer.respond_to?(:instance)
|
40
|
+
observer.instance
|
41
|
+
else
|
42
|
+
raise ArgumentError, "#{observer} must be a lowercase, underscored class name (or an instance of the class itself) responding to the instance method. Example: Person.observers = :big_brother # calls BigBrother.instance"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
protected
|
48
|
+
# Notify observers when the observed class is subclassed.
|
49
|
+
def inherited(subclass)
|
50
|
+
super
|
51
|
+
changed
|
52
|
+
notify_observers :observed_class_inherited, subclass
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# Observer classes respond to lifecycle callbacks to implement trigger-like
|
58
|
+
# behavior outside the original class. This is a great way to reduce the
|
59
|
+
# clutter that normally comes when the model class is burdened with
|
60
|
+
# functionality that doesn't pertain to the core responsibility of the
|
61
|
+
# class. Example:
|
62
|
+
#
|
63
|
+
# class CommentObserver < ActiveObject::Observer
|
64
|
+
# def after_save(comment)
|
65
|
+
# Notifications.deliver_comment("admin@do.com", "New comment was posted", comment)
|
66
|
+
# end
|
67
|
+
# end
|
68
|
+
#
|
69
|
+
# This Observer sends an email when a Comment#save is finished.
|
70
|
+
#
|
71
|
+
# class ContactObserver < ActiveObject::Observer
|
72
|
+
# def after_create(contact)
|
73
|
+
# contact.logger.info('New contact added!')
|
74
|
+
# end
|
75
|
+
#
|
76
|
+
# def after_destroy(contact)
|
77
|
+
# contact.logger.warn("Contact with an id of #{contact.id} was destroyed!")
|
78
|
+
# end
|
79
|
+
# end
|
80
|
+
#
|
81
|
+
# This Observer uses logger to log when specific callbacks are triggered.
|
82
|
+
#
|
83
|
+
# == Observing a class that can't be inferred
|
84
|
+
#
|
85
|
+
# Observers will by default be mapped to the class with which they share a name. So CommentObserver will
|
86
|
+
# be tied to observing Comment, ProductManagerObserver to ProductManager, and so on. If you want to name your observer
|
87
|
+
# differently than the class you're interested in observing, you can use the Observer.observe class method which takes
|
88
|
+
# either the concrete class (Product) or a symbol for that class (:product):
|
89
|
+
#
|
90
|
+
# class AuditObserver < ActiveObject::Observer
|
91
|
+
# observe :account
|
92
|
+
#
|
93
|
+
# def after_update(account)
|
94
|
+
# AuditTrail.new(account, "UPDATED")
|
95
|
+
# end
|
96
|
+
# end
|
97
|
+
#
|
98
|
+
# If the audit observer needs to watch more than one kind of object, this can be specified with multiple arguments:
|
99
|
+
#
|
100
|
+
# class AuditObserver < ActiveObject::Observer
|
101
|
+
# observe :account, :balance
|
102
|
+
#
|
103
|
+
# def after_update(record)
|
104
|
+
# AuditTrail.new(record, "UPDATED")
|
105
|
+
# end
|
106
|
+
# end
|
107
|
+
#
|
108
|
+
# The AuditObserver will now act on both updates to Account and Balance by treating them both as records.
|
109
|
+
#
|
110
|
+
# == Available callback methods
|
111
|
+
#
|
112
|
+
# The observer can implement callback methods for each of the methods described in the Callbacks module.
|
113
|
+
#
|
114
|
+
#
|
115
|
+
# == Loading
|
116
|
+
#
|
117
|
+
# Observers register themselves in the model class they observe, since it is the class that
|
118
|
+
# notifies them of events when they occur. As a side-effect, when an observer is loaded its
|
119
|
+
# corresponding model class is loaded.
|
120
|
+
#
|
121
|
+
# If by any chance you are using observed models in the initialization you can still
|
122
|
+
# load their observers by calling <tt>ModelObserver.instance</tt> before. Observers are
|
123
|
+
# singletons and that call instantiates and registers them.
|
124
|
+
#
|
125
|
+
class Observer
|
126
|
+
include Singleton
|
127
|
+
|
128
|
+
class << self
|
129
|
+
# Attaches the observer to the supplied model classes.
|
130
|
+
def observe(*models)
|
131
|
+
models.flatten!
|
132
|
+
models.collect! { |model| model.is_a?(Symbol) ? model.to_s.camelize.constantize : model }
|
133
|
+
define_method(:observed_classes) { Set.new(models) }
|
134
|
+
end
|
135
|
+
|
136
|
+
# The class observed by default is inferred from the observer's class name:
|
137
|
+
# assert_equal Person, PersonObserver.observed_class
|
138
|
+
def observed_class
|
139
|
+
if observed_class_name = name[/(.*)Observer/, 1]
|
140
|
+
observed_class_name.constantize
|
141
|
+
else
|
142
|
+
nil
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
# Start observing the declared classes and their subclasses.
|
148
|
+
def initialize
|
149
|
+
Set.new(observed_classes + observed_subclasses).each { |klass| add_observer! klass }
|
150
|
+
end
|
151
|
+
|
152
|
+
# Send observed_method(object) if the method exists.
|
153
|
+
def update(observed_method, object) #:nodoc:
|
154
|
+
send(observed_method, object) if respond_to?(observed_method)
|
155
|
+
end
|
156
|
+
|
157
|
+
# Special method sent by the observed class when it is inherited.
|
158
|
+
# Passes the new subclass.
|
159
|
+
def observed_class_inherited(subclass) #:nodoc:
|
160
|
+
self.class.observe(observed_classes + [subclass])
|
161
|
+
add_observer!(subclass)
|
162
|
+
end
|
163
|
+
|
164
|
+
protected
|
165
|
+
def observed_classes
|
166
|
+
Set.new([self.class.observed_class].compact.flatten)
|
167
|
+
end
|
168
|
+
|
169
|
+
def observed_subclasses
|
170
|
+
observed_classes.sum([]) { |klass| klass.send(:subclasses) }
|
171
|
+
end
|
172
|
+
|
173
|
+
def add_observer!(klass)
|
174
|
+
klass.add_observer(self)
|
175
|
+
if respond_to?(:after_find) && !klass.method_defined?(:after_find)
|
176
|
+
klass.class_eval 'def after_find() end'
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
module ActiveObject #: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.attributes
|
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
|
+
objects = objects.objects if objects.is_a?(ActiveObject::Associations::HasManyAssociation::Collection)
|
58
|
+
|
59
|
+
unless objects.nil?
|
60
|
+
association_options = include_has_options ? include_associations[association] : base_only_or_except
|
61
|
+
opts = options.merge(association_options)
|
62
|
+
yield(association, objects, opts)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
options[:include] = include_associations
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def serializable_object
|
71
|
+
returning(serializable_object = {}) do
|
72
|
+
serializable_names.each { |name| serializable_object[name] = @object.send(name) }
|
73
|
+
|
74
|
+
add_includes do |association, objects, opts|
|
75
|
+
if objects.is_a?(Enumerable)
|
76
|
+
serializable_object[association] = objects.collect { |r| self.class.new(r, opts).serializable_object }
|
77
|
+
serializable_object.delete("#{association}_ids")
|
78
|
+
else
|
79
|
+
serializable_object[association] = self.class.new(objects, opts).serializable_object
|
80
|
+
serializable_object.delete("#{association}_id")
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def serialize
|
88
|
+
# overwrite to implement
|
89
|
+
end
|
90
|
+
|
91
|
+
def to_s(&block)
|
92
|
+
serialize(&block)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
require 'active_object/serializers/xml_serializer'
|
99
|
+
require 'active_object/serializers/json_serializer'
|
@@ -0,0 +1,75 @@
|
|
1
|
+
module ActiveObject #: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 < ActiveObject::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
|