class Jbuilder
def self.deep_format_keys(value = true)
def self.deep_format_keys(value = true) @@deep_format_keys = value end
def self.encode(*args, &block)
def self.encode(*args, &block) new(*args, &block).target! end
def self.ignore_nil(value = true)
def self.ignore_nil(value = true) @@ignore_nil = value end
def self.key_format(*args)
def self.key_format(*args) @@key_formatter = KeyFormatter.new(*args) end
def _blank?(value=@attributes)
def _blank?(value=@attributes) BLANK == value end
def _extract_hash_values(object, attributes)
def _extract_hash_values(object, attributes) attributes.each{ |key| _set_value key, _format_keys(object.fetch(key)) } end
def _extract_method_values(object, attributes)
def _extract_method_values(object, attributes) attributes.each{ |key| _set_value key, _format_keys(object.public_send(key)) } end
def _format_keys(hash_or_array)
def _format_keys(hash_or_array) return hash_or_array unless @deep_format_keys if ::Array === hash_or_array hash_or_array.map { |value| _format_keys(value) } elsif ::Hash === hash_or_array ::Hash[hash_or_array.collect { |k, v| [_key(k), _format_keys(v)] }] else hash_or_array end end
def _is_collection?(object)
def _is_collection?(object) _object_respond_to?(object, :map, :count) && NON_ENUMERABLES.none?{ |klass| klass === object } end
def _key(key)
def _key(key) @key_formatter ? @key_formatter.format(key) : key.to_s end
def _map_collection(collection)
def _map_collection(collection) collection.map do |element| _scope{ yield element } end - [BLANK] end
def _merge_block(key)
def _merge_block(key) current_value = _blank? ? BLANK : @attributes.fetch(_key(key), BLANK) ::Kernel.raise NullError.build(key) if current_value.nil? new_value = _scope{ yield self } _merge_values(current_value, new_value) end
def _merge_values(current_value, updates)
def _merge_values(current_value, updates) if _blank?(updates) current_value elsif _blank?(current_value) || updates.nil? || current_value.empty? && ::Array === updates updates elsif ::Array === current_value && ::Array === updates current_value + updates elsif ::Hash === current_value && ::Hash === updates current_value.deep_merge(updates) else ::Kernel.raise MergeError.build(current_value, updates) end end
def _object_respond_to?(object, *methods)
def _object_respond_to?(object, *methods) methods.all?{ |m| object.respond_to?(m) } end
def _scope
def _scope parent_attributes, parent_formatter, parent_deep_format_keys = @attributes, @key_formatter, @deep_format_keys @attributes = BLANK yield @attributes ensure @attributes, @key_formatter, @deep_format_keys = parent_attributes, parent_formatter, parent_deep_format_keys end
def _set_value(key, value)
def _set_value(key, value) ::Kernel.raise NullError.build(key) if @attributes.nil? ::Kernel.raise ArrayError.build(key) if ::Array === @attributes return if @ignore_nil && value.nil? or _blank?(value) @attributes = {} if _blank? @attributes[_key(key)] = value end
def array!(collection = [], *attributes, &block)
json.array! [1, 2, 3]
If you omit the block then you can set the top level array directly:
{ "people": [ { "name": David", "age": 32 }, { "name": Jamie", "age": 31 } ] }
end
json.age calculate_age(person.birthday)
json.name person.name
json.people(@people) do |person|
It's generally only needed to use this method for top-level arrays. If you have named arrays, you can do:
json.(@people) { |person| ... }
You can use the call syntax instead of an explicit extract! call:
[ { "name": David", "age": 32 }, { "name": Jamie", "age": 31 } ]
end
json.age calculate_age(person.birthday)
json.name person.name
json.array!(@people) do |person|
Example:
an element of the resulting array.
Turns the current element into an array and iterates over the passed collection, adding each iteration as
def array!(collection = [], *attributes, &block) array = if collection.nil? [] elsif ::Kernel.block_given? _map_collection(collection, &block) elsif attributes.any? _map_collection(collection) { |element| extract! element, *attributes } else _format_keys(collection.to_a) end @attributes = _merge_values(@attributes, array) end
def attributes!
def attributes! @attributes end
def call(object, *attributes, &block)
def call(object, *attributes, &block) if ::Kernel.block_given? array! object, &block else extract! object, *attributes end end
def child!
json.content comment.formatted_content
json.comments(@post.comments) do |comment|
More commonly, you'd use the combined iterator, though:
{ "comments": [ { "content": "hello" }, { "content": "world" } ]}
end
json.child! { json.content "world" }
json.child! { json.content "hello" }
json.comments do
Example:
Turns the current element into an array and yields a builder to add a hash.
def child! @attributes = [] unless ::Array === @attributes @attributes << _scope{ yield self } end
def deep_format_keys!(value = true)
{ "settings": { "someValue": "abc" }}
json.settings({some_value: "abc"})
json.deep_format_keys!
json.key_format! camelize: :lower
{ "settings": { "some_value": "abc" }}
json.settings({some_value: "abc"})
json.key_format! camelize: :lower
Example:
methods like set!, merge! or array!.
Deeply apply key format to nested hashes and arrays passed to
def deep_format_keys!(value = true) @deep_format_keys = value end
def extract!(object, *attributes)
You can also use the call syntax instead of an explicit extract! call:
{ "name": David", "age": 32 }, { "name": Jamie", "age": 31 }
json.extract! @person, :name, :age
@person = { name: 'David', age: 32 }
or you can utilize a Hash
@person = Struct.new(:name, :age).new('David', 32)
Example:
Extracts the mentioned attributes or hash elements from the passed object and turns them into attributes of the JSON.
def extract!(object, *attributes) if ::Hash === object _extract_hash_values(object, attributes) else _extract_method_values(object, attributes) end end
def ignore_nil!(value = true)
{}
json.id User.new.id
json.ignore_nil!
{ "id": null }
json.id User.new.id
json.ignore_nil! false
Example:
not to receive keys which have null values.
for JSON clients that don't deal well with nil values, and would prefer
If you want to skip adding nil values to your JSON hash. This is useful
def ignore_nil!(value = true) @ignore_nil = value end
def initialize(options = {})
def initialize(options = {}) @attributes = {} @key_formatter = options.fetch(:key_formatter){ @@key_formatter ? @@key_formatter.clone : nil} @ignore_nil = options.fetch(:ignore_nil, @@ignore_nil) @deep_format_keys = options.fetch(:deep_format_keys, @@deep_format_keys) yield self if ::Kernel.block_given? end
def key_format!(*args)
{ "_first_name": "David" }
json.first_name "David"
json.key_format! ->(key){ "_" + key }
Lambdas can also be used.
{ "firstName": "David" }
json.first_name "David"
json.key_format! camelize: :lower
You can pass parameters to the method using a hash pair.
{ "AUTHOR": { "NAME": "David", "AGE": 32 } }
end
json.age 32
json.name "David"
json.author do
json.key_format! :upcase
Example:
the key. You can also pass in lambdas for more complex transformations.
will cause that function to be called on the key. So :upcase will upper case
Specifies formatting to be applied to the key. Passing in a name of a function
def key_format!(*args) @key_formatter = KeyFormatter.new(*args) end
def merge!(object)
def merge!(object) hash_or_array = ::Jbuilder === object ? object.attributes! : object @attributes = _merge_values(@attributes, _format_keys(hash_or_array)) end
def method_missing(*args, &block)
def method_missing(*args, &block) if ::Kernel.block_given? set!(*args, &block) else set!(*args) end end
def nil!
def nil! @attributes = nil end
def set!(key, value = BLANK, *args, &block)
def set!(key, value = BLANK, *args, &block) result = if ::Kernel.block_given? if !_blank?(value) # json.comments @post.comments { |comment| ... } # { "comments": [ { ... }, { ... } ] } _scope{ array! value, &block } else # json.comments { ... } # { "comments": ... } _merge_block(key){ yield self } end elsif args.empty? if ::Jbuilder === value # json.age 32 # json.person another_jbuilder # { "age": 32, "person": { ... } _format_keys(value.attributes!) else # json.age 32 # { "age": 32 } _format_keys(value) end elsif _is_collection?(value) # json.comments @post.comments, :content, :created_at # { "comments": [ { "content": "hello", "created_at": "..." }, { "content": "world", "created_at": "..." } ] } _scope{ array! value, *args } else # json.author @post.creator, :name, :email_address # { "author": { "name": "David", "email_address": "david@loudthinking.com" } } _merge_block(key){ extract! value, *args } end _set_value key, result end
def target!
def target! @attributes.to_json end