activeobject 0.0.3
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 +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
|