georgepalmer-couch_foo 0.7.4 → 0.7.7
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/README.rdoc +1 -1
- data/VERSION.yml +1 -1
- data/lib/couch_foo.rb +3 -1
- data/lib/couch_foo/attribute_methods.rb +1 -0
- data/lib/couch_foo/base.rb +7 -1
- data/lib/couch_foo/serialization.rb +98 -0
- data/lib/couch_foo/serializers/json_serializer.rb +81 -0
- data/lib/couch_foo/serializers/xml_serializer.rb +347 -0
- metadata +6 -2
data/README.rdoc
CHANGED
@@ -18,7 +18,7 @@ are a few minor differences to the way CouchDB works. In particular:
|
|
18
18
|
* The price of index updating is paid when next accessing the index rather than the point of insertion. This can be more efficient or less depending on your application. It may make sense to use an external process to do the updating for you - see CouchFoo#find for more on this
|
19
19
|
* On that note, occasional compacting of CouchDB is required to recover space from old versions of documents. This can be kicked off in several ways (see quick start guide)
|
20
20
|
|
21
|
-
It is recommend that you read the quick start and performance sections in the rdoc for a full overview of differences and points to be aware of when developing.
|
21
|
+
It is recommend that you read the quick start and performance sections in the rdoc for a full overview of differences and points to be aware of when developing. The Changelog file shows the differences between gem versions and should be checked when upgrading gem versions.
|
22
22
|
|
23
23
|
|
24
24
|
== Getting started
|
data/VERSION.yml
CHANGED
data/lib/couch_foo.rb
CHANGED
@@ -20,7 +20,8 @@ require 'couch_foo/associations'
|
|
20
20
|
#require 'active_record/association_preload'
|
21
21
|
#require 'active_record/aggregations'
|
22
22
|
require 'couch_foo/timestamp'
|
23
|
-
require '
|
23
|
+
require 'couch_foo/calculations'
|
24
|
+
require 'couch_foo/serialization'
|
24
25
|
require 'couch_foo/attribute_methods'
|
25
26
|
require 'couch_foo/dirty'
|
26
27
|
|
@@ -40,4 +41,5 @@ CouchFoo::Base.class_eval do
|
|
40
41
|
# include ActiveRecord::Aggregations
|
41
42
|
include CouchFoo::Reflection
|
42
43
|
include CouchFoo::Calculations
|
44
|
+
include CouchFoo::Serialization
|
43
45
|
end
|
data/lib/couch_foo/base.rb
CHANGED
@@ -32,6 +32,12 @@ module CouchFoo
|
|
32
32
|
class DocumentConflict < CouchFooError
|
33
33
|
end
|
34
34
|
|
35
|
+
# The types that are permitted for properties. At the moment this is just used
|
36
|
+
# to determine whether a .to_xml call should be made on the type during
|
37
|
+
# serialization but I imagine it will be used to enforce type checking as well
|
38
|
+
# at a later date
|
39
|
+
AVAILABLE_TYPES = [String, Integer, Float, DateTime, Time, Date, TrueClass, Boolean]
|
40
|
+
|
35
41
|
# Simple class encapsulating a property
|
36
42
|
class Property
|
37
43
|
attr_accessor :name, :type, :default
|
@@ -1027,7 +1033,7 @@ module CouchFoo
|
|
1027
1033
|
define_attr_method :inheritance_column, value, &block
|
1028
1034
|
end
|
1029
1035
|
alias :inheritance_column= :set_inheritance_column
|
1030
|
-
|
1036
|
+
|
1031
1037
|
# Set a property for the document. These can be passed a type and options hash. If no type
|
1032
1038
|
# is passed a #to_json method is called on the ruby object and the result stored in the
|
1033
1039
|
# database. When it is retrieved from the database a class.from_json(json) method is called
|
@@ -0,0 +1,98 @@
|
|
1
|
+
module CouchFoo #:nodoc:
|
2
|
+
module Serialization
|
3
|
+
class Serializer #:nodoc:
|
4
|
+
attr_reader :options
|
5
|
+
|
6
|
+
def initialize(record, options = {})
|
7
|
+
@record, @options = record, options.dup
|
8
|
+
end
|
9
|
+
|
10
|
+
# To replicate the behavior in CouchFoo#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 = @record.attribute_names
|
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]) | Array(@record.class.inheritance_column)
|
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 @record.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
|
+
# +records+ - the association record(s) to be serialized
|
45
|
+
# +opts+ - options for the association records
|
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
|
+
records = case @record.class.reflect_on_association(association).macro
|
56
|
+
when :has_many, :has_and_belongs_to_many
|
57
|
+
@record.send(association).to_a
|
58
|
+
when :has_one, :belongs_to
|
59
|
+
@record.send(association)
|
60
|
+
end
|
61
|
+
|
62
|
+
unless records.nil?
|
63
|
+
association_options = include_has_options ? include_associations[association] : base_only_or_except
|
64
|
+
opts = options.merge(association_options)
|
65
|
+
yield(association, records, opts)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
options[:include] = include_associations
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def serializable_record
|
74
|
+
returning(serializable_record = {}) do
|
75
|
+
serializable_names.each { |name| serializable_record[name] = @record.send(:read_attribute_before_type_cast, name) }
|
76
|
+
add_includes do |association, records, opts|
|
77
|
+
if records.is_a?(Enumerable)
|
78
|
+
serializable_record[association] = records.collect { |r| self.class.new(r, opts).serializable_record }
|
79
|
+
else
|
80
|
+
serializable_record[association] = self.class.new(records, opts).serializable_record
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def serialize
|
87
|
+
# overwrite to implement
|
88
|
+
end
|
89
|
+
|
90
|
+
def to_s(&block)
|
91
|
+
serialize(&block)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
require 'couch_foo/serializers/xml_serializer'
|
98
|
+
require 'couch_foo/serializers/json_serializer'
|
@@ -0,0 +1,81 @@
|
|
1
|
+
module CouchFoo #: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
|
+
def from_json(json)
|
64
|
+
self.attributes = ActiveSupport::JSON.decode(json)
|
65
|
+
self
|
66
|
+
end
|
67
|
+
|
68
|
+
class JsonSerializer < CouchFoo::Serialization::Serializer #:nodoc:
|
69
|
+
def serialize
|
70
|
+
puts serializable_record.inspect
|
71
|
+
serializable_record.to_json
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
module ClassMethods
|
76
|
+
def json_class_name
|
77
|
+
@json_class_name ||= name.demodulize.underscore.inspect
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,347 @@
|
|
1
|
+
module CouchFoo #:nodoc:
|
2
|
+
module Serialization
|
3
|
+
# Builds an XML document to represent the model. Some configuration is
|
4
|
+
# available through +options+. However more complicated cases should
|
5
|
+
# override CouchFoo::Base#to_xml. This is a necessary step if you wish
|
6
|
+
# to use custom types
|
7
|
+
#
|
8
|
+
# By default the generated XML document will include the processing
|
9
|
+
# instruction and all the object's attributes. For example:
|
10
|
+
#
|
11
|
+
# <?xml version="1.0" encoding="UTF-8"?>
|
12
|
+
# <topic>
|
13
|
+
# <title>The First Topic</title>
|
14
|
+
# <author-name>David</author-name>
|
15
|
+
# <id type="integer">1</id>
|
16
|
+
# <approved type="boolean">false</approved>
|
17
|
+
# <replies-count type="integer">0</replies-count>
|
18
|
+
# <bonus-time type="datetime">2000-01-01T08:28:00+12:00</bonus-time>
|
19
|
+
# <written-on type="datetime">2003-07-16T09:28:00+1200</written-on>
|
20
|
+
# <content>Have a nice day</content>
|
21
|
+
# <author-email-address>david@loudthinking.com</author-email-address>
|
22
|
+
# <parent-id></parent-id>
|
23
|
+
# <last-read type="date">2004-04-15</last-read>
|
24
|
+
# </topic>
|
25
|
+
#
|
26
|
+
# This behavior can be controlled with <tt>:only</tt>, <tt>:except</tt>,
|
27
|
+
# <tt>:skip_instruct</tt>, <tt>:skip_types</tt> and <tt>:dasherize</tt>.
|
28
|
+
# The <tt>:only</tt> and <tt>:except</tt> options are the same as for the
|
29
|
+
# +attributes+ method. The default is to dasherize all column names, but you
|
30
|
+
# can disable this setting <tt>:dasherize</tt> to +false+. To not have the
|
31
|
+
# column type included in the XML output set <tt>:skip_types</tt> to +true+.
|
32
|
+
#
|
33
|
+
# For instance:
|
34
|
+
#
|
35
|
+
# topic.to_xml(:skip_instruct => true, :except => [ :id, :bonus_time, :written_on, :replies_count ])
|
36
|
+
#
|
37
|
+
# <topic>
|
38
|
+
# <title>The First Topic</title>
|
39
|
+
# <author-name>David</author-name>
|
40
|
+
# <approved type="boolean">false</approved>
|
41
|
+
# <content>Have a nice day</content>
|
42
|
+
# <author-email-address>david@loudthinking.com</author-email-address>
|
43
|
+
# <parent-id></parent-id>
|
44
|
+
# <last-read type="date">2004-04-15</last-read>
|
45
|
+
# </topic>
|
46
|
+
#
|
47
|
+
# To include first level associations use <tt>:include</tt>:
|
48
|
+
#
|
49
|
+
# firm.to_xml :include => [ :account, :clients ]
|
50
|
+
#
|
51
|
+
# <?xml version="1.0" encoding="UTF-8"?>
|
52
|
+
# <firm>
|
53
|
+
# <id type="integer">1</id>
|
54
|
+
# <rating type="integer">1</rating>
|
55
|
+
# <name>37signals</name>
|
56
|
+
# <clients type="array">
|
57
|
+
# <client>
|
58
|
+
# <rating type="integer">1</rating>
|
59
|
+
# <name>Summit</name>
|
60
|
+
# </client>
|
61
|
+
# <client>
|
62
|
+
# <rating type="integer">1</rating>
|
63
|
+
# <name>Microsoft</name>
|
64
|
+
# </client>
|
65
|
+
# </clients>
|
66
|
+
# <account>
|
67
|
+
# <id type="integer">1</id>
|
68
|
+
# <credit-limit type="integer">50</credit-limit>
|
69
|
+
# </account>
|
70
|
+
# </firm>
|
71
|
+
#
|
72
|
+
# To include deeper levels of associations pass a hash like this:
|
73
|
+
#
|
74
|
+
# firm.to_xml :include => {:account => {}, :clients => {:include => :address}}
|
75
|
+
# <?xml version="1.0" encoding="UTF-8"?>
|
76
|
+
# <firm>
|
77
|
+
# <id type="integer">1</id>
|
78
|
+
# <rating type="integer">1</rating>
|
79
|
+
# <name>37signals</name>
|
80
|
+
# <clients type="array">
|
81
|
+
# <client>
|
82
|
+
# <rating type="integer">1</rating>
|
83
|
+
# <name>Summit</name>
|
84
|
+
# <address>
|
85
|
+
# ...
|
86
|
+
# </address>
|
87
|
+
# </client>
|
88
|
+
# <client>
|
89
|
+
# <rating type="integer">1</rating>
|
90
|
+
# <name>Microsoft</name>
|
91
|
+
# <address>
|
92
|
+
# ...
|
93
|
+
# </address>
|
94
|
+
# </client>
|
95
|
+
# </clients>
|
96
|
+
# <account>
|
97
|
+
# <id type="integer">1</id>
|
98
|
+
# <credit-limit type="integer">50</credit-limit>
|
99
|
+
# </account>
|
100
|
+
# </firm>
|
101
|
+
#
|
102
|
+
# To include any methods on the model being called use <tt>:methods</tt>:
|
103
|
+
#
|
104
|
+
# firm.to_xml :methods => [ :calculated_earnings, :real_earnings ]
|
105
|
+
#
|
106
|
+
# <firm>
|
107
|
+
# # ... normal attributes as shown above ...
|
108
|
+
# <calculated-earnings>100000000000000000</calculated-earnings>
|
109
|
+
# <real-earnings>5</real-earnings>
|
110
|
+
# </firm>
|
111
|
+
#
|
112
|
+
# To call any additional Procs use <tt>:procs</tt>. The Procs are passed a
|
113
|
+
# modified version of the options hash that was given to +to_xml+:
|
114
|
+
#
|
115
|
+
# proc = Proc.new { |options| options[:builder].tag!('abc', 'def') }
|
116
|
+
# firm.to_xml :procs => [ proc ]
|
117
|
+
#
|
118
|
+
# <firm>
|
119
|
+
# # ... normal attributes as shown above ...
|
120
|
+
# <abc>def</abc>
|
121
|
+
# </firm>
|
122
|
+
#
|
123
|
+
# Alternatively, you can yield the builder object as part of the +to_xml+ call:
|
124
|
+
#
|
125
|
+
# firm.to_xml do |xml|
|
126
|
+
# xml.creator do
|
127
|
+
# xml.first_name "David"
|
128
|
+
# xml.last_name "Heinemeier Hansson"
|
129
|
+
# end
|
130
|
+
# end
|
131
|
+
#
|
132
|
+
# <firm>
|
133
|
+
# # ... normal attributes as shown above ...
|
134
|
+
# <creator>
|
135
|
+
# <first_name>David</first_name>
|
136
|
+
# <last_name>Heinemeier Hansson</last_name>
|
137
|
+
# </creator>
|
138
|
+
# </firm>
|
139
|
+
#
|
140
|
+
# As noted above, you may override +to_xml+ in your CouchFoo::Base
|
141
|
+
# subclasses to have complete control about what's generated. The general
|
142
|
+
# form of doing this is:
|
143
|
+
#
|
144
|
+
# class IHaveMyOwnXML < CouchFoo::Base
|
145
|
+
# def to_xml(options = {})
|
146
|
+
# options[:indent] ||= 2
|
147
|
+
# xml = options[:builder] ||= Builder::XmlMarkup.new(:indent => options[:indent])
|
148
|
+
# xml.instruct! unless options[:skip_instruct]
|
149
|
+
# xml.level_one do
|
150
|
+
# xml.tag!(:second_level, 'content')
|
151
|
+
# end
|
152
|
+
# end
|
153
|
+
# end
|
154
|
+
def to_xml(options = {}, &block)
|
155
|
+
serializer = XmlSerializer.new(self, options)
|
156
|
+
block_given? ? serializer.to_s(&block) : serializer.to_s
|
157
|
+
end
|
158
|
+
|
159
|
+
def from_xml(xml)
|
160
|
+
self.attributes = Hash.from_xml(xml).values.first
|
161
|
+
self
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
class XmlSerializer < CouchFoo::Serialization::Serializer #:nodoc:
|
166
|
+
def builder
|
167
|
+
@builder ||= begin
|
168
|
+
options[:indent] ||= 2
|
169
|
+
builder = options[:builder] ||= Builder::XmlMarkup.new(:indent => options[:indent])
|
170
|
+
|
171
|
+
unless options[:skip_instruct]
|
172
|
+
builder.instruct!
|
173
|
+
options[:skip_instruct] = true
|
174
|
+
end
|
175
|
+
|
176
|
+
builder
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
def root
|
181
|
+
root = (options[:root] || @record.class.to_s.underscore).to_s
|
182
|
+
dasherize? ? root.dasherize : root
|
183
|
+
end
|
184
|
+
|
185
|
+
def dasherize?
|
186
|
+
!options.has_key?(:dasherize) || options[:dasherize]
|
187
|
+
end
|
188
|
+
|
189
|
+
def serializable_attributes
|
190
|
+
serializable_attribute_names.collect { |name| Attribute.new(name, @record) }
|
191
|
+
end
|
192
|
+
|
193
|
+
def serializable_method_attributes
|
194
|
+
Array(options[:methods]).inject([]) do |method_attributes, name|
|
195
|
+
method_attributes << MethodAttribute.new(name.to_s, @record) if @record.respond_to?(name.to_s)
|
196
|
+
method_attributes
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
def add_attributes
|
201
|
+
(serializable_attributes + serializable_method_attributes).each do |attribute|
|
202
|
+
add_tag(attribute)
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
def add_procs
|
207
|
+
if procs = options.delete(:procs)
|
208
|
+
[ *procs ].each do |proc|
|
209
|
+
proc.call(options)
|
210
|
+
end
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
def add_tag(attribute)
|
215
|
+
builder.tag!(
|
216
|
+
dasherize? ? attribute.name.dasherize : attribute.name,
|
217
|
+
attribute.value.to_s,
|
218
|
+
attribute.decorations(!options[:skip_types])
|
219
|
+
)
|
220
|
+
end
|
221
|
+
|
222
|
+
def add_associations(association, records, opts)
|
223
|
+
if records.is_a?(Enumerable)
|
224
|
+
tag = association.to_s
|
225
|
+
tag = tag.dasherize if dasherize?
|
226
|
+
if records.empty?
|
227
|
+
builder.tag!(tag, :type => :array)
|
228
|
+
else
|
229
|
+
builder.tag!(tag, :type => :array) do
|
230
|
+
association_name = association.to_s.singularize
|
231
|
+
records.each do |record|
|
232
|
+
record.to_xml opts.merge(
|
233
|
+
:root => association_name,
|
234
|
+
:type => (record.class.to_s.underscore == association_name ? nil : record.class.name)
|
235
|
+
)
|
236
|
+
end
|
237
|
+
end
|
238
|
+
end
|
239
|
+
else
|
240
|
+
if record = @record.send(association)
|
241
|
+
record.to_xml(opts.merge(:root => association))
|
242
|
+
end
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
def serialize
|
247
|
+
args = [root]
|
248
|
+
if options[:namespace]
|
249
|
+
args << {:xmlns=>options[:namespace]}
|
250
|
+
end
|
251
|
+
|
252
|
+
if options[:type]
|
253
|
+
args << {:type=>options[:type]}
|
254
|
+
end
|
255
|
+
|
256
|
+
builder.tag!(*args) do
|
257
|
+
add_attributes
|
258
|
+
procs = options.delete(:procs)
|
259
|
+
add_includes { |association, records, opts| add_associations(association, records, opts) }
|
260
|
+
options[:procs] = procs
|
261
|
+
add_procs
|
262
|
+
yield builder if block_given?
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
class Attribute #:nodoc:
|
267
|
+
attr_reader :name, :value, :type
|
268
|
+
|
269
|
+
def initialize(name, record)
|
270
|
+
@name, @record = name, record
|
271
|
+
|
272
|
+
@type = compute_type
|
273
|
+
@value = compute_value
|
274
|
+
end
|
275
|
+
|
276
|
+
# There is a significant speed improvement if the value
|
277
|
+
# does not need to be escaped, as <tt>tag!</tt> escapes all values
|
278
|
+
# to ensure that valid XML is generated. For known binary
|
279
|
+
# values, it is at least an order of magnitude faster to
|
280
|
+
# Base64 encode binary values and directly put them in the
|
281
|
+
# output XML than to pass the original value or the Base64
|
282
|
+
# encoded value to the <tt>tag!</tt> method. It definitely makes
|
283
|
+
# no sense to Base64 encode the value and then give it to
|
284
|
+
# <tt>tag!</tt>, since that just adds additional overhead.
|
285
|
+
def needs_encoding?
|
286
|
+
![ :binary, :date, :datetime, :boolean, :float, :integer ].include?(type)
|
287
|
+
end
|
288
|
+
|
289
|
+
def decorations(include_types = true)
|
290
|
+
decorations = {}
|
291
|
+
|
292
|
+
if type == :binary
|
293
|
+
decorations[:encoding] = 'base64'
|
294
|
+
end
|
295
|
+
|
296
|
+
if include_types && type != :string
|
297
|
+
decorations[:type] = type
|
298
|
+
end
|
299
|
+
|
300
|
+
if value.nil?
|
301
|
+
decorations[:nil] = true
|
302
|
+
end
|
303
|
+
|
304
|
+
decorations
|
305
|
+
end
|
306
|
+
|
307
|
+
protected
|
308
|
+
def compute_type
|
309
|
+
type = @record.class.property_types[name.to_sym]
|
310
|
+
|
311
|
+
# Hack until get these types into properties structure
|
312
|
+
if name.to_sym == :_id || name.to_sym == :_rev || name.to_sym == :ruby_class
|
313
|
+
type = String
|
314
|
+
end
|
315
|
+
|
316
|
+
case type
|
317
|
+
when Time
|
318
|
+
Datetime
|
319
|
+
when Boolean
|
320
|
+
TrueClass
|
321
|
+
else
|
322
|
+
type
|
323
|
+
end
|
324
|
+
end
|
325
|
+
|
326
|
+
def compute_value
|
327
|
+
value = @record.send(name)
|
328
|
+
|
329
|
+
# Custom types must implement .to_xml
|
330
|
+
if !CouchFoo::AVAILABLE_TYPES.include?(type)
|
331
|
+
value.to_xml unless value.nil?
|
332
|
+
elsif formatter = Hash::XML_FORMATTING[type.to_s]
|
333
|
+
value ? formatter.call(value) : nil
|
334
|
+
else
|
335
|
+
value
|
336
|
+
end
|
337
|
+
end
|
338
|
+
end
|
339
|
+
|
340
|
+
class MethodAttribute < Attribute #:nodoc:
|
341
|
+
protected
|
342
|
+
def compute_type
|
343
|
+
Hash::XML_TYPE_NAMES[@record.send(name).class.name] || :string
|
344
|
+
end
|
345
|
+
end
|
346
|
+
end
|
347
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: georgepalmer-couch_foo
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.7.
|
4
|
+
version: 0.7.7
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- George Palmer
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-02-
|
12
|
+
date: 2009-02-09 00:00:00 -08:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -64,6 +64,7 @@ files:
|
|
64
64
|
- lib/couch_foo
|
65
65
|
- lib/couch_foo/database.rb
|
66
66
|
- lib/couch_foo/dirty.rb
|
67
|
+
- lib/couch_foo/serialization.rb
|
67
68
|
- lib/couch_foo/associations.rb
|
68
69
|
- lib/couch_foo/associations
|
69
70
|
- lib/couch_foo/associations/has_one_association.rb
|
@@ -75,6 +76,9 @@ files:
|
|
75
76
|
- lib/couch_foo/associations/has_and_belongs_to_many_association.rb
|
76
77
|
- lib/couch_foo/observer.rb
|
77
78
|
- lib/couch_foo/base.rb
|
79
|
+
- lib/couch_foo/serializers
|
80
|
+
- lib/couch_foo/serializers/xml_serializer.rb
|
81
|
+
- lib/couch_foo/serializers/json_serializer.rb
|
78
82
|
- lib/couch_foo/calculations.rb
|
79
83
|
- lib/couch_foo/view_methods.rb
|
80
84
|
- lib/couch_foo/attribute_methods.rb
|