jbuilder 0.4.0 → 0.4.2

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -66,6 +66,34 @@ end
66
66
  # => [ { "name": David", "age": 32 }, { "name": Jamie", "age": 31 } ]
67
67
  ```
68
68
 
69
+ Jbuilder objects can be directly nested inside each other. Useful for composing objects.
70
+
71
+ ``` ruby
72
+ class Person
73
+ # ... Class Definition ... #
74
+ def to_builder
75
+ person = Jbuilder.new
76
+ person.(self, :name, :age)
77
+ person
78
+ end
79
+ end
80
+
81
+ class Company
82
+ # ... Class Definition ... #
83
+ def to_builder
84
+ company = Jbuilder.new
85
+ company.name name
86
+ company.president president.to_builder
87
+ company
88
+ end
89
+ end
90
+
91
+ company = Company.new("Doodle Corp", Person.new("John Stobs", 58))
92
+ company.to_builder.target!
93
+
94
+ # => {"name":"Doodle Corp","president":{"name":"John Stobs","age":58}}
95
+ ```
96
+
69
97
  You can either use Jbuilder stand-alone or directly as an ActionView template language. When required in Rails, you can create views ala show.json.jbuilder (the json is already yielded):
70
98
 
71
99
  ``` ruby
@@ -90,10 +118,25 @@ end
90
118
  json.partial! "api/comments/comments", comments: @message.comments
91
119
  ```
92
120
 
121
+ Keys can be auto formatted using `key_format!`, this can be used to convert keynames from the standard ruby_format to CamelCase:
122
+
123
+ ``` ruby
124
+ json.key_format! :camelize => :lower
125
+ json.first_name "David"
126
+
127
+ # { "firstName": "David" }
128
+ ```
129
+
130
+ You can set this globaly with the class method `key_format` (from inside your enviorment.rb for example):
131
+
132
+ ``` ruby
133
+ Jbuilder.key_format :camelize => :lower
134
+ ```
135
+
93
136
  Libraries similar to this in some form or another include:
94
137
 
95
138
  * RABL: https://github.com/nesquena/rabl
96
139
  * JsonBuilder: https://github.com/nov/jsonbuilder
97
140
  * JSON Builder: https://github.com/dewski/json_builder
98
141
  * Jsonify: https://github.com/bsiggelkow/jsonify
99
- * RepresentationView: https://github.com/mdub/representative_view
142
+ * RepresentationView: https://github.com/mdub/representative_view
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'jbuilder'
3
- s.version = '0.4.0'
3
+ s.version = '0.4.2'
4
4
  s.author = 'David Heinemeier Hansson'
5
5
  s.email = 'david@37signals.com'
6
6
  s.summary = 'Create JSON structures via a Builder-style DSL'
@@ -3,23 +3,84 @@ require 'active_support/ordered_hash'
3
3
  require 'active_support/core_ext/array/access'
4
4
  require 'active_support/core_ext/enumerable'
5
5
  require 'active_support/json'
6
-
6
+ require 'multi_json'
7
7
  class Jbuilder < BlankSlate
8
8
  # Yields a builder and automatically turns the result into a JSON string
9
9
  def self.encode
10
10
  new._tap { |jbuilder| yield jbuilder }.target!
11
11
  end
12
12
 
13
+ @@key_format = {}
14
+
13
15
  define_method(:__class__, find_hidden_method(:class))
14
16
  define_method(:_tap, find_hidden_method(:tap))
17
+ define_method(:_is_a?, find_hidden_method(:is_a?))
18
+ reveal(:respond_to?)
15
19
 
16
- def initialize
20
+ def initialize(key_format = @@key_format.clone)
17
21
  @attributes = ActiveSupport::OrderedHash.new
22
+ @key_format = key_format
18
23
  end
19
24
 
20
25
  # Dynamically set a key value pair.
21
- def set!(key, value)
22
- @attributes[key] = value
26
+ #
27
+ # Example:
28
+ #
29
+ # json.set!(:each, "stuff")
30
+ #
31
+ # { "each": "stuff" }
32
+ #
33
+ # You can also pass a block for nested attributes
34
+ #
35
+ # json.set!(:author) do |json|
36
+ # json.name "David"
37
+ # json.age 32
38
+ # end
39
+ #
40
+ # { "author": { "name": "David", "age": 32 } }
41
+ def set!(key, value = nil)
42
+ if block_given?
43
+ _yield_nesting(key) { |jbuilder| yield jbuilder }
44
+ else
45
+ @attributes[_format_key(key)] = value
46
+ end
47
+ end
48
+
49
+ # Specifies formatting to be applied to the key. Passing in a name of a function
50
+ # will cause that function to be called on the key. So :upcase will upper case
51
+ # the key. You can also pass in lambdas for more complex transformations.
52
+ #
53
+ # Example:
54
+ #
55
+ # json.key_format! :upcase
56
+ # json.author do |json|
57
+ # json.name "David"
58
+ # json.age 32
59
+ # end
60
+ #
61
+ # { "AUTHOR": { "NAME": "David", "AGE": 32 } }
62
+ #
63
+ # You can pass parameters to the method using a hash pair.
64
+ #
65
+ # json.key_format! :camelize => :lower
66
+ # json.first_name "David"
67
+ #
68
+ # { "firstName": "David" }
69
+ #
70
+ # Lambdas can also be used.
71
+ #
72
+ # json.key_format! ->(key){ "_" + key }
73
+ # json.first_name "David"
74
+ #
75
+ # { "_first_name": "David" }
76
+ #
77
+ def key_format!(*args)
78
+ __class__.extract_key_format(args, @key_format)
79
+ end
80
+
81
+ # Same as the instance method key_format! except sets the default.
82
+ def self.key_format(*args)
83
+ extract_key_format(args, @@key_format)
23
84
  end
24
85
 
25
86
  # Turns the current element into an array and yields a builder to add a hash.
@@ -112,13 +173,19 @@ class Jbuilder < BlankSlate
112
173
 
113
174
  # Encodes the current builder as JSON.
114
175
  def target!
115
- ActiveSupport::JSON.encode @attributes
176
+ MultiJson.encode @attributes
116
177
  end
117
178
 
118
179
 
119
180
  private
120
181
  def method_missing(method, *args)
121
182
  case
183
+ # json.age 32
184
+ # json.person another_jbuilder
185
+ # { "age": 32, "person": { ... }
186
+ when args.one? && args.first.respond_to?(:_is_a?) && args.first._is_a?(Jbuilder)
187
+ set! method, args.first.attributes!
188
+
122
189
  # json.comments @post.comments { |json, comment| ... }
123
190
  # { "comments": [ { ... }, { ... } ] }
124
191
  when args.one? && block_given?
@@ -133,7 +200,7 @@ class Jbuilder < BlankSlate
133
200
  # { "comments": ... }
134
201
  when args.empty? && block_given?
135
202
  _yield_nesting(method) { |jbuilder| yield jbuilder }
136
-
203
+
137
204
  # json.comments(@post.comments, :content, :created_at)
138
205
  # { "comments": [ { "content": "hello", "created_at": "..." }, { "content": "world", "created_at": "..." } ] }
139
206
  when args.many? && args.first.is_a?(Enumerable)
@@ -148,7 +215,7 @@ class Jbuilder < BlankSlate
148
215
 
149
216
  # Overwrite in subclasses if you need to add initialization values
150
217
  def _new_instance
151
- __class__.new
218
+ __class__.new(@key_format)
152
219
  end
153
220
 
154
221
  def _yield_nesting(container)
@@ -180,6 +247,28 @@ class Jbuilder < BlankSlate
180
247
  def _inline_extract(container, record, attributes)
181
248
  __send__(container) { |parent| parent.extract! record, *attributes }
182
249
  end
250
+
251
+ # Format the key using the methods described in @key_format
252
+ def _format_key(key)
253
+ @key_format.inject(key.to_s) do |result, args|
254
+ func, args = args
255
+ if func.is_a? Proc
256
+ func.call(result, *args)
257
+ else
258
+ result.send(func, *args)
259
+ end
260
+ end
261
+ end
262
+
263
+ def self.extract_key_format(args, target)
264
+ options = args.extract_options!
265
+ args.each do |name|
266
+ target[name] = []
267
+ end
268
+ options.each do |name, paramaters|
269
+ target[name] = paramaters
270
+ end
271
+ end
183
272
  end
184
273
 
185
274
  require "jbuilder_template" if defined?(ActionView::Template)
@@ -7,11 +7,18 @@ class JbuilderTemplate < Jbuilder
7
7
  @context = context
8
8
  super()
9
9
  end
10
-
11
- def partial!(partial_name, options = {})
12
- @context.render(partial_name, options.merge(:json => self))
10
+
11
+ def partial!(options, locals = {})
12
+ case options
13
+ when Hash
14
+ options[:locals] ||= {}
15
+ options[:locals].merge!(:json => self)
16
+ @context.render(options)
17
+ else
18
+ @context.render(options, locals.merge(:json => self))
19
+ end
13
20
  end
14
-
21
+
15
22
  private
16
23
  def _new_instance
17
24
  __class__.new(@context)
@@ -23,15 +30,10 @@ class JbuilderHandler
23
30
  self.default_format = Mime::JSON
24
31
 
25
32
  def self.call(template)
26
- %{
27
- if defined?(json)
28
- #{template.source}
29
- else
30
- JbuilderTemplate.encode(self) do |json|
31
- #{template.source}
32
- end
33
- end
34
- }
33
+
34
+ # this juggling is required to keep line numbers right in the error
35
+ %{__already_defined = defined?(json); json||=JbuilderTemplate.new(self); #{template.source}
36
+ json.target! unless __already_defined}
35
37
  end
36
38
  end
37
39
 
@@ -1,5 +1,6 @@
1
1
  require 'test/unit'
2
2
  require 'active_support/test_case'
3
+ require 'active_support/inflector'
3
4
 
4
5
  require 'jbuilder'
5
6
 
@@ -207,7 +208,19 @@ class JbuilderTest < ActiveSupport::TestCase
207
208
 
208
209
  assert_equal "david", JSON.parse(json)["comments"].first["authors"].first["name"]
209
210
  end
210
-
211
+
212
+ test "nested jbuilder objects" do
213
+ to_nest = Jbuilder.new
214
+ to_nest.nested_value "Nested Test"
215
+ json = Jbuilder.encode do |json|
216
+ json.value "Test"
217
+ json.nested to_nest
218
+ end
219
+ parsed = JSON.parse(json)
220
+ assert_equal "Test", parsed['value']
221
+ assert_equal "Nested Test", parsed["nested"]["nested_value"]
222
+ end
223
+
211
224
  test "top-level array" do
212
225
  comments = [ Struct.new(:content, :id).new("hello", 1), Struct.new(:content, :id).new("world", 2) ]
213
226
 
@@ -242,4 +255,80 @@ class JbuilderTest < ActiveSupport::TestCase
242
255
 
243
256
  assert_equal "stuff", JSON.parse(json)["each"]
244
257
  end
258
+
259
+ test "dynamically set a key/nested child with block" do
260
+ json = Jbuilder.encode do |json|
261
+ json.set!(:author) do |json|
262
+ json.name "David"
263
+ json.age 32
264
+ end
265
+ end
266
+
267
+ JSON.parse(json).tap do |parsed|
268
+ assert_equal "David", parsed["author"]["name"]
269
+ assert_equal 32, parsed["author"]["age"]
270
+ end
271
+ end
272
+
273
+ test "key_format! with parameter" do
274
+ json = Jbuilder.new
275
+ json.key_format! :camelize => [:lower]
276
+ json.camel_style "for JS"
277
+
278
+ assert_equal ['camelStyle'], json.attributes!.keys
279
+ end
280
+
281
+ test "key_format! with parameter not as an array" do
282
+ json = Jbuilder.new
283
+ json.key_format! :camelize => :lower
284
+ json.camel_style "for JS"
285
+
286
+ assert_equal ['camelStyle'], json.attributes!.keys
287
+ end
288
+
289
+ test "key_format! propagates to child elements" do
290
+ json = Jbuilder.new
291
+ json.key_format! :upcase
292
+ json.level1 "one"
293
+ json.level2 do |json|
294
+ json.value "two"
295
+ end
296
+
297
+ result = json.attributes!
298
+ assert_equal "one", result["LEVEL1"]
299
+ assert_equal "two", result["LEVEL2"]["VALUE"]
300
+ end
301
+
302
+ test "key_format! with no parameter" do
303
+ json = Jbuilder.new
304
+ json.key_format! :upcase
305
+ json.lower "Value"
306
+
307
+ assert_equal ['LOWER'], json.attributes!.keys
308
+ end
309
+
310
+ test "key_format! with multiple steps" do
311
+ json = Jbuilder.new
312
+ json.key_format! :upcase, :pluralize
313
+ json.pill ""
314
+
315
+ assert_equal ["PILLs"], json.attributes!.keys
316
+ end
317
+
318
+ test "key_format! with lambda/proc" do
319
+ json = Jbuilder.new
320
+ json.key_format! ->(key){ key + " and friends" }
321
+ json.oats ""
322
+
323
+ assert_equal ["oats and friends"], json.attributes!.keys
324
+ end
325
+
326
+ test "default key_format!" do
327
+ Jbuilder.key_format :camelize => :lower
328
+ json = Jbuilder.new
329
+ json.camel_style "for JS"
330
+
331
+ assert_equal ['camelStyle'], json.attributes!.keys
332
+ Jbuilder.class_variable_set("@@key_format", {})
333
+ end
245
334
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jbuilder
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.4.2
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-03-23 00:00:00.000000000 Z
12
+ date: 2012-08-08 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activesupport
16
- requirement: &70191736347120 !ruby/object:Gem::Requirement
16
+ requirement: &2152793760 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: 3.0.0
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *70191736347120
24
+ version_requirements: *2152793760
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: blankslate
27
- requirement: &70191736346420 !ruby/object:Gem::Requirement
27
+ requirement: &2152793020 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ! '>='
@@ -32,7 +32,7 @@ dependencies:
32
32
  version: 2.1.2.4
33
33
  type: :runtime
34
34
  prerelease: false
35
- version_requirements: *70191736346420
35
+ version_requirements: *2152793020
36
36
  description:
37
37
  email: david@37signals.com
38
38
  executables: []
@@ -41,9 +41,6 @@ extra_rdoc_files: []
41
41
  files:
42
42
  - ./Gemfile
43
43
  - ./Gemfile.lock
44
- - ./jbuilder-0.3.1.gem
45
- - ./jbuilder-0.3.2.gem
46
- - ./jbuilder-0.3.gem
47
44
  - ./jbuilder.gemspec
48
45
  - ./lib/jbuilder.rb
49
46
  - ./lib/jbuilder_template.rb
@@ -70,7 +67,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
70
67
  version: '0'
71
68
  requirements: []
72
69
  rubyforge_project:
73
- rubygems_version: 1.8.15
70
+ rubygems_version: 1.8.7
74
71
  signing_key:
75
72
  specification_version: 3
76
73
  summary: Create JSON structures via a Builder-style DSL
Binary file
Binary file
Binary file