5
5
require 'active_support/json'
6
6
require 'multi_json'
7
7
class Jbuilder < BlankSlate
8
+ class KeyFormatter
9
+ def initialize ( *args )
10
+ @format = { }
11
+ @cache = { }
12
+
13
+ options = args . extract_options!
14
+ args . each do |name |
15
+ @format [ name ] = [ ]
16
+ end
17
+ options . each do |name , paramaters |
18
+ @format [ name ] = paramaters
19
+ end
20
+ end
21
+
22
+ def initialize_copy ( original )
23
+ @cache = { }
24
+ end
25
+
26
+ def format ( key )
27
+ @cache [ key ] ||= @format . inject ( key . to_s ) do |result , args |
28
+ func , args = args
29
+ if func . is_a? Proc
30
+ func . call ( result , *args )
31
+ else
32
+ result . send ( func , *args )
33
+ end
34
+ end
35
+ end
36
+ end
37
+
8
38
# Yields a builder and automatically turns the result into a JSON string
9
39
def self . encode
10
40
new . _tap { |jbuilder | yield jbuilder } . target!
11
41
end
12
42
13
- @@key_format = { }
43
+ @@key_formatter = KeyFormatter . new
14
44
15
45
define_method ( :__class__ , find_hidden_method ( :class ) )
16
46
define_method ( :_tap , find_hidden_method ( :tap ) )
17
- define_method ( :_is_a? , find_hidden_method ( :is_a? ) )
18
47
reveal ( :respond_to? )
19
48
20
- def initialize ( key_format = @@key_format . clone )
49
+ def initialize ( key_formatter = @@key_formatter . clone )
21
50
@attributes = ActiveSupport ::OrderedHash . new
22
- @key_format = key_format
51
+ @key_formatter = key_formatter
23
52
end
24
53
25
54
# Dynamically set a key value pair.
@@ -42,7 +71,7 @@ def set!(key, value = nil)
42
71
if block_given?
43
72
_yield_nesting ( key ) { |jbuilder | yield jbuilder }
44
73
else
45
- @attributes [ _format_key ( key ) ] = value
74
+ _set_value ( key , value )
46
75
end
47
76
end
48
77
@@ -75,12 +104,12 @@ def set!(key, value = nil)
75
104
# { "_first_name": "David" }
76
105
#
77
106
def key_format! ( *args )
78
- __class__ . extract_key_format ( args , @key_format )
107
+ @key_formatter = KeyFormatter . new ( * args )
79
108
end
80
109
81
110
# Same as the instance method key_format! except sets the default.
82
111
def self . key_format ( *args )
83
- extract_key_format ( args , @@key_format )
112
+ @@key_formatter = KeyFormatter . new ( * args )
84
113
end
85
114
86
115
# Turns the current element into an array and yields a builder to add a hash.
@@ -129,12 +158,9 @@ def child!
129
158
#
130
159
# { "people": [ { "name": David", "age": 32 }, { "name": Jamie", "age": 31 } ] }
131
160
def array! ( collection )
132
- @attributes = [ ] and return if collection . empty?
133
-
134
- collection . each do |element |
135
- child! do |child |
136
- yield child , element
137
- end
161
+ @attributes = [ ]
162
+ collection . each do |element | #[] and return if collection.empty?
163
+ @attributes << _new_instance . _tap { |jbuilder | yield jbuilder , element } . attributes!
138
164
end
139
165
end
140
166
@@ -156,22 +182,19 @@ def array!(collection)
156
182
#
157
183
# json.(@person, :name, :age)
158
184
def extract! ( object , *attributes )
159
- p = if object . is_a? ( Hash )
160
- lambda { |attribute | __send__ attribute , object . send ( :fetch , attribute ) }
185
+ if object . is_a? ( Hash )
186
+ attributes . each { |attribute | _set_value attribute , object . send ( :fetch , attribute ) }
161
187
else
162
- lambda { |attribute | __send__ attribute , object . send ( attribute ) }
188
+ attributes . each { |attribute | _set_value attribute , object . send ( attribute ) }
163
189
end
164
-
165
- attributes . each { |attribute | p . call ( attribute ) }
166
190
end
167
191
168
192
if RUBY_VERSION > '1.9'
169
- def call ( *args )
170
- case
171
- when args . one?
172
- array! ( args . first ) { |json , element | yield json , element }
173
- when args . many?
174
- extract! ( *args )
193
+ def call ( object = nil , *attributes )
194
+ if attributes . empty?
195
+ array! ( object ) { |json , element | yield json , element }
196
+ else
197
+ extract! ( object , *attributes )
175
198
end
176
199
end
177
200
end
@@ -187,97 +210,79 @@ def target!
187
210
end
188
211
189
212
190
- private
191
- def method_missing ( method , *args )
192
- case
193
- # json.age 32
194
- # json.person another_jbuilder
195
- # { "age": 32, "person": { ... }
196
- when args . one? && args . first . respond_to? ( :_is_a? ) && args . first . _is_a? ( Jbuilder )
197
- set! method , args . first . attributes!
198
-
199
- # json.comments @post.comments { |json, comment| ... }
200
- # { "comments": [ { ... }, { ... } ] }
201
- when args . one? && block_given?
202
- _yield_iteration ( method , args . first ) { |child , element | yield child , element }
203
-
204
- # json.age 32
205
- # { "age": 32 }
206
- when args . length == 1
207
- set! method , args . first
208
-
209
- # json.comments { |json| ... }
210
- # { "comments": ... }
211
- when args . empty? && block_given?
212
- _yield_nesting ( method ) { |jbuilder | yield jbuilder }
213
+ protected
214
+ def _set_value ( key , value )
215
+ @attributes [ @key_formatter . format ( key ) ] = value
216
+ end
213
217
214
- # json.comments(@post.comments, :content, :created_at)
215
- # { "comments": [ { "content": "hello", "created_at": "..." }, { "content": "world", "created_at": "..." } ] }
216
- when args . many? && args . first . respond_to? ( :each )
217
- _inline_nesting method , args . first , args . from ( 1 )
218
218
219
- # json.author @post.creator, :name, :email_address
220
- # { "author": { "name": "David", "email_address": "[email protected] " } }
221
- when args . many?
222
- _inline_extract method , args . first , args . from ( 1 )
219
+ private
220
+ def method_missing ( method , value = nil , *args )
221
+ if block_given?
222
+ if value
223
+ # json.comments @post.comments { |json, comment| ... }
224
+ # { "comments": [ { ... }, { ... } ] }
225
+ _yield_iteration ( method , value ) { |child , element | yield child , element }
226
+ else
227
+ # json.comments { |json| ... }
228
+ # { "comments": ... }
229
+ _yield_nesting ( method ) { |jbuilder | yield jbuilder }
230
+ end
231
+ else
232
+ if args . empty?
233
+ if Jbuilder === value
234
+ # json.age 32
235
+ # json.person another_jbuilder
236
+ # { "age": 32, "person": { ... }
237
+ _set_value method , value . attributes!
238
+ else
239
+ # json.age 32
240
+ # { "age": 32 }
241
+ _set_value method , value
242
+ end
243
+ else
244
+ if value . respond_to? ( :each )
245
+ # json.comments(@post.comments, :content, :created_at)
246
+ # { "comments": [ { "content": "hello", "created_at": "..." }, { "content": "world", "created_at": "..." } ] }
247
+ _inline_nesting method , value , args
248
+ else
249
+ # json.author @post.creator, :name, :email_address
250
+ # { "author": { "name": "David", "email_address": "[email protected] " } }
251
+ _inline_extract method , value , args
252
+ end
253
+ end
223
254
end
224
255
end
225
256
226
257
# Overwrite in subclasses if you need to add initialization values
227
258
def _new_instance
228
- __class__ . new ( @key_format )
259
+ __class__ . new ( @key_formatter )
229
260
end
230
261
231
262
def _yield_nesting ( container )
232
- set! container , _new_instance . _tap { |jbuilder | yield jbuilder } . attributes!
263
+ _set_value container , _new_instance . _tap { |jbuilder | yield jbuilder } . attributes!
233
264
end
234
265
235
266
def _inline_nesting ( container , collection , attributes )
236
- __send__ ( container ) do |parent |
237
- parent . array! ( collection ) and return if collection . empty?
238
-
239
- collection . each do |element |
240
- parent . child! do |child |
241
- attributes . each do |attribute |
242
- child . __send__ attribute , element . send ( attribute )
243
- end
267
+ _yield_nesting ( container ) do |parent |
268
+ parent . array! ( collection ) do |child , element |
269
+ attributes . each do |attribute |
270
+ child . _set_value attribute , element . send ( attribute )
244
271
end
245
272
end
246
273
end
247
274
end
248
275
249
276
def _yield_iteration ( container , collection )
250
- __send__ ( container ) do |parent |
277
+ _yield_nesting ( container ) do |parent |
251
278
parent . array! ( collection ) do |child , element |
252
279
yield child , element
253
280
end
254
281
end
255
282
end
256
283
257
284
def _inline_extract ( container , record , attributes )
258
- __send__ ( container ) { |parent | parent . extract! record , *attributes }
259
- end
260
-
261
- # Format the key using the methods described in @key_format
262
- def _format_key ( key )
263
- @key_format . inject ( key . to_s ) do |result , args |
264
- func , args = args
265
- if func . is_a? Proc
266
- func . call ( result , *args )
267
- else
268
- result . send ( func , *args )
269
- end
270
- end
271
- end
272
-
273
- def self . extract_key_format ( args , target )
274
- options = args . extract_options!
275
- args . each do |name |
276
- target [ name ] = [ ]
277
- end
278
- options . each do |name , paramaters |
279
- target [ name ] = paramaters
280
- end
285
+ _yield_nesting ( container ) { |parent | parent . extract! record , *attributes }
281
286
end
282
287
end
283
288
0 commit comments