jbuilder 0.4.0 → 0.4.2

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.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