Skip to content

Removing BlankSlate and simplifying implementation #59

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Sep 17, 2012
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
PATH
remote: .
specs:
jbuilder (0.4.1)
jbuilder (0.5.0)
activesupport (>= 3.0.0)
blankslate (>= 2.1.2.4)

GEM
remote: http://rubygems.org/
Expand All @@ -24,7 +23,6 @@ GEM
activesupport (3.2.8)
i18n (~> 0.6)
multi_json (~> 1.0)
blankslate (2.1.2.4)
builder (3.0.0)
erubis (2.7.0)
hike (1.2.1)
Expand Down
1 change: 0 additions & 1 deletion jbuilder.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ Gem::Specification.new do |s|
s.summary = 'Create JSON structures via a Builder-style DSL'

s.add_dependency 'activesupport', '>= 3.0.0'
s.add_dependency 'blankslate', '>= 2.1.2.4'

s.files = Dir["#{File.dirname(__FILE__)}/**/*"]
end
117 changes: 50 additions & 67 deletions lib/jbuilder.rb
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
require 'blankslate'
require 'active_support/basic_object'
require 'active_support/ordered_hash'
require 'active_support/core_ext/array/access'
require 'active_support/core_ext/enumerable'
require 'active_support/json'
require 'multi_json'
class Jbuilder < BlankSlate

class Jbuilder < ActiveSupport::BasicObject
class KeyFormatter
def initialize(*args)
@format = {}
Expand Down Expand Up @@ -34,20 +35,18 @@ def format(key)
end
end
end

# Yields a builder and automatically turns the result into a JSON string
def self.encode
new._tap { |jbuilder| yield jbuilder }.target!
def self.encode(*args)
jbuilder = new(*args)
yield jbuilder
jbuilder.target!
end

@@key_formatter = KeyFormatter.new

define_method(:__class__, find_hidden_method(:class))
define_method(:_tap, find_hidden_method(:tap))
reveal(:respond_to?)

def initialize(key_formatter = @@key_formatter.clone)
@attributes = ActiveSupport::OrderedHash.new
@attributes = ::ActiveSupport::OrderedHash.new
@key_formatter = key_formatter
end

Expand All @@ -68,8 +67,8 @@ def initialize(key_formatter = @@key_formatter.clone)
#
# { "author": { "name": "David", "age": 32 } }
def set!(key, value = nil)
if block_given?
_yield_nesting(key) { |jbuilder| yield jbuilder }
if ::Kernel::block_given?
_set_value(key, _with_attributes { yield self })
else
_set_value(key, value)
end
Expand Down Expand Up @@ -106,7 +105,7 @@ def set!(key, value = nil)
def key_format!(*args)
@key_formatter = KeyFormatter.new(*args)
end

# Same as the instance method key_format! except sets the default.
def self.key_format(*args)
@@key_formatter = KeyFormatter.new(*args)
Expand All @@ -127,13 +126,13 @@ def self.key_format(*args)
#
# json.comments(@post.comments) do |json, comment|
# json.content comment.formatted_content
# end
# end
def child!
@attributes = [] unless @attributes.is_a? Array
@attributes << _new_instance._tap { |jbuilder| yield jbuilder }.attributes!
@attributes = [] unless @attributes.is_a? ::Array
@attributes << _with_attributes { yield self }
end

# Turns the current element into an array and iterates over the passed collection, adding each iteration as
# Turns the current element into an array and iterates over the passed collection, adding each iteration as
# an element of the resulting array.
#
# Example:
Expand All @@ -154,14 +153,11 @@ def child!
# json.people(@people) do |json, person|
# json.name person.name
# json.age calculate_age(person.birthday)
# end
# end
#
# { "people": [ { "name": David", "age": 32 }, { "name": Jamie", "age": 31 } ] }
def array!(collection)
@attributes = []
collection.each do |element| #[] and return if collection.empty?
@attributes << _new_instance._tap { |jbuilder| yield jbuilder, element }.attributes!
end
@attributes = _map_collection(collection) { |element| yield self, element }
end

# Extracts the mentioned attributes or hash elements from the passed object and turns them into attributes of the JSON.
Expand All @@ -182,31 +178,29 @@ def array!(collection)
#
# json.(@person, :name, :age)
def extract!(object, *attributes)
if object.is_a?(Hash)
if object.is_a?(::Hash)
attributes.each {|attribute| _set_value attribute, object.send(:fetch, attribute)}
else
attributes.each {|attribute| _set_value attribute, object.send(attribute)}
end
end

if RUBY_VERSION > '1.9'
def call(object = nil, *attributes)
if attributes.empty?
array!(object) { |json, element| yield json, element }
else
extract!(object, *attributes)
end
def call(object = nil, *attributes)
if attributes.empty?
array!(object) { |_, element| yield self, element }
else
extract!(object, *attributes)
end
end

# Returns the attributes of the current builder.
def attributes!
@attributes
end

# Encodes the current builder as JSON.
def target!
MultiJson.encode @attributes
::MultiJson.encode @attributes
end


Expand All @@ -218,71 +212,60 @@ def _set_value(key, value)

private
def method_missing(method, value = nil, *args)
if block_given?
result = if ::Kernel.block_given?
if value
# json.comments @post.comments { |json, comment| ... }
# { "comments": [ { ... }, { ... } ] }
_yield_iteration(method, value) { |child, element| yield child, element }
_map_collection(value) { |element| yield self, element }
else
# json.comments { |json| ... }
# { "comments": ... }
_yield_nesting(method) { |jbuilder| yield jbuilder }
_with_attributes { yield self }
end
else
if args.empty?
if Jbuilder === value
if ::Jbuilder === value
# json.age 32
# json.person another_jbuilder
# { "age": 32, "person": { ... }
_set_value method, value.attributes!
value.attributes!
else
# json.age 32
# { "age": 32 }
_set_value method, value
value
end
else
if value.respond_to?(:each)
# json.comments(@post.comments, :content, :created_at)
# { "comments": [ { "content": "hello", "created_at": "..." }, { "content": "world", "created_at": "..." } ] }
_inline_nesting method, value, args
_map_collection(value) do |element|
args.each do |attribute|
_set_value attribute, element.send(attribute)
end
end
else
# json.author @post.creator, :name, :email_address
# { "author": { "name": "David", "email_address": "david@loudthinking.com" } }
_inline_extract method, value, args
_with_attributes { extract! value, *args }
end
end
end
_set_value method, result
end

# Overwrite in subclasses if you need to add initialization values
def _new_instance
__class__.new(@key_formatter)
end

def _yield_nesting(container)
_set_value container, _new_instance._tap { |jbuilder| yield jbuilder }.attributes!
end

def _inline_nesting(container, collection, attributes)
_yield_nesting(container) do |parent|
parent.array!(collection) do |child, element|
attributes.each do |attribute|
child._set_value attribute, element.send(attribute)
end
end
def _map_collection(collection)
collection.each.map do |element|
_with_attributes { yield element }
end
end

def _yield_iteration(container, collection)
_yield_nesting(container) do |parent|
parent.array!(collection) do |child, element|
yield child, element
end
end
end

def _inline_extract(container, record, attributes)
_yield_nesting(container) { |parent| parent.extract! record, *attributes }

def _with_attributes
parent_attributes, parent_formatter = @attributes, @key_formatter
@attributes = ::ActiveSupport::OrderedHash.new
yield
@attributes
ensure
@attributes, @key_formatter = parent_attributes, parent_formatter
end
end

Expand Down
10 changes: 0 additions & 10 deletions lib/jbuilder_template.rb
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
class JbuilderTemplate < Jbuilder
def self.encode(context)
new(context)._tap { |jbuilder| yield jbuilder }.target!
end

def initialize(context, *args)
@context = context
super(*args)
Expand All @@ -18,19 +14,13 @@ def partial!(options, locals = {})
@context.render(options, locals.merge(:json => self))
end
end

private
def _new_instance
__class__.new(@context, @key_formatter)
end
end

class JbuilderHandler
cattr_accessor :default_format
self.default_format = Mime::JSON

def self.call(template)

# this juggling is required to keep line numbers right in the error
%{__already_defined = defined?(json); json||=JbuilderTemplate.new(self); #{template.source}
json.target! unless __already_defined}
Expand Down
4 changes: 2 additions & 2 deletions test/jbuilder_template_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ class JbuilderTemplateTest < ActiveSupport::TestCase
json = JbuilderTemplate.encode(binding) do |json|
json.content "hello"
end
assert_equal "hello", JSON.parse(json)["content"]

assert_equal "hello", MultiJson.load(json)["content"]
end

test "key_format! with parameter" do
Expand Down
Loading