bldr 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,2 @@
1
+ .rspec
2
+ Gemfile.lock
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm 1.9.2
data/.travis.yml ADDED
@@ -0,0 +1,7 @@
1
+ rvm:
2
+ - 1.8.7
3
+ - 1.9.1
4
+ - 1.9.2
5
+ - ree
6
+ - ruby-head
7
+ - rbx
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ source :rubygems
2
+
3
+ gemspec
4
+
5
+ gem "rake"
6
+
7
+ group :test do
8
+ gem 'autotest'
9
+ gem "rspec", ">= 2.6.0"
10
+ end
data/MIT-LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ Copyright (c) 2011 Alex Sharp
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+
data/README.md ADDED
@@ -0,0 +1,281 @@
1
+ [![Build Status](http://travis-ci.org/ajsharp/bldr.png)](http://travis-ci.org/ajsharp/bldr)
2
+
3
+
4
+ # Bldr
5
+
6
+ Bldr is a minimalist templating library that provides a simple DSL for generating
7
+ json documents from ruby objects. It currently supports Sinatra out of
8
+ the box -- Rails 3 support is planned for the near future.
9
+
10
+ If you would like to contribute, pull requests with specs are warmly accepted :)
11
+
12
+ ## Features
13
+
14
+ * Simple json templating DSL
15
+ * Uses Tilt's built-in rendering and template caching for better
16
+ performance
17
+
18
+ ## Installation
19
+
20
+ There are two ways to use Bldr in your Sinatra app, depending on whether
21
+ you are using Sinatra's classic or module application style:
22
+
23
+ ```ruby
24
+
25
+ # Method 1: Classic style
26
+
27
+ require 'sinatra/bldr'
28
+
29
+ get '/hello' do
30
+ bldr :hello
31
+ end
32
+
33
+
34
+ # Method 2: Modular style
35
+
36
+ require 'sinatra/bldr'
37
+
38
+ class MyApp < Sinatra::Base
39
+ register Sinatra::Bldr
40
+ end
41
+ ```
42
+
43
+ ## Usage
44
+
45
+ In your sinatra endpoints/actions, use the `bldr` helper method to
46
+ render templates.
47
+
48
+ ```ruby
49
+ get "/posts" do
50
+ # ...
51
+ posts = Post.all
52
+ bldr :'template.json', {}, {:posts => posts}
53
+ end
54
+
55
+ # views/template.json.bldr
56
+ collection :posts => posts do
57
+ attributes :title
58
+ attribute :comment_count { |post| post.comments.count }
59
+
60
+ collection :comments => current_object.comments do
61
+ attributes :body, :author, :email
62
+ end
63
+ end
64
+ ```
65
+
66
+ ## Examples
67
+
68
+ ### Rendering a simple list of attributes
69
+
70
+ ```ruby
71
+ object :post => post do
72
+ attributes :title, :body
73
+ end
74
+ ```
75
+
76
+ Output:
77
+
78
+ ```javascript
79
+ {
80
+ "post": {
81
+ "title": "my title",
82
+ "body": "..."
83
+ }
84
+ }
85
+ ```
86
+
87
+ ### Dynamic attributes
88
+
89
+ ```ruby
90
+ object :post => post do
91
+ attribute :comment_count do |post|
92
+ post.comments.count
93
+ end
94
+ end
95
+ ```
96
+
97
+ Output:
98
+
99
+ ```javascript
100
+ {
101
+ "post": {
102
+ "comment_count": 1
103
+ }
104
+ }
105
+
106
+ ```
107
+
108
+ ### Attribute aliases
109
+
110
+ ```ruby
111
+ object :post => post do
112
+ attributes :title, :body
113
+
114
+ object :author => post.author do
115
+ attribute :surname => :last_name
116
+ end
117
+ end
118
+ ```
119
+
120
+ Output:
121
+
122
+ ```javascript
123
+ {
124
+ "post": {
125
+ "title": "my title",
126
+ "body": "...",
127
+ "author": {
128
+ "surname": "Doe"
129
+ }
130
+ }
131
+ }
132
+ ```
133
+
134
+ ### Nested objects
135
+
136
+ ```ruby
137
+ object :post => post do
138
+ attributes :title, :body
139
+
140
+ object :author => post.author do
141
+ attributes :first_name, :last_name, :email
142
+
143
+ attribute(:full_name) { |author| "#{author.first_name} #{author.last_name}" }
144
+ end
145
+ end
146
+ ```
147
+
148
+ Output:
149
+
150
+ ```javascript
151
+ {
152
+ "post": {
153
+ "title": "my title",
154
+ "body": "...",
155
+ "author": {
156
+ "first_name": "John",
157
+ "last_name": "Doe",
158
+ "email": "john@doe.com",
159
+ "full_name": "John Doe"
160
+ }
161
+ }
162
+ }
163
+ ```
164
+
165
+ ### Root-level attributes
166
+
167
+ ```ruby
168
+ get '/redirector' do
169
+ url = params['redirect_url']
170
+ bldr :'redirect.json', :locals => {:url => url}
171
+ end
172
+
173
+ # views/redirect.json.bldr
174
+ object do
175
+ attribute(:redirect_to) { url }
176
+ end
177
+ ```
178
+
179
+ Output:
180
+
181
+ ```javascript
182
+ {"redirect_to": "http://example.org"}
183
+ ```
184
+
185
+ ### Collections
186
+
187
+ All the examples above can be used inside a collection block. Here we
188
+ assume a Post model which has many Comments. You might use the below
189
+ code to render an action which returns a collection of posts, where
190
+ each post has a collection of comments.
191
+
192
+ ```ruby
193
+ collection :posts => posts do
194
+ attributes :title
195
+ attribute :comment_count { |post| post.comments.count }
196
+
197
+ # current_object
198
+ collection :comments => current_object.comments do
199
+ attributes :body, :author_name, :author_email
200
+ end
201
+ end
202
+ ```
203
+
204
+ Output:
205
+
206
+ ```javascript
207
+ {
208
+ "posts": [
209
+ {
210
+ "title": "my title",
211
+ "comment_count": 2,
212
+ "comments": [
213
+ {
214
+ "body": "...",
215
+ "author_name": "Comment Troll",
216
+ "email": "troll@trolling.edu"
217
+ },
218
+ {
219
+ "body": "...",
220
+ "author_name": "Uber Troll",
221
+ "email": "uber.troll@earthlink.net"
222
+ }
223
+ ]
224
+ }
225
+ ]
226
+ }
227
+ ```
228
+
229
+ When inside of a collection block, you can use the `current_object`
230
+ method to access the member of the collection currently being iterated
231
+ over. This allows you to do nested collections, as in the example above.
232
+
233
+ ### Templates
234
+
235
+ It is recommended to name your templates with the content type extension before
236
+ the .bldr extension. For example: `my_template.json.bldr`.
237
+
238
+ The templates themselves are just plain ruby code. They are evaluated in the context of a
239
+ `Bldr::Node` instance, which provides the bldr DSL. The DSL is comprised
240
+ primarily of 3 simple methods:
241
+
242
+ + `object` - Creates an object
243
+ + `collection` - Iterates over a collection of objects
244
+ + `attributes` - Add attributes to the current object.
245
+
246
+ ### Local Variables
247
+
248
+ You may pass local variables from your sinatra actions to bldr templates
249
+ by passing the `bldr` method a `:locals` hash, like so:
250
+
251
+ ```ruby
252
+ get '/posts' do
253
+ posts = Post.all.recent
254
+
255
+ bldr :'posts/index.json', :locals => {:posts => posts}
256
+ end
257
+ ```
258
+
259
+ ## Editor Syntax Support
260
+
261
+ To get proper syntax highlighting in vim, add this line to your .vimrc:
262
+
263
+ ```
264
+ au BufRead,BufNewFile *.bldr set filetype=ruby
265
+ ```
266
+
267
+ ## TODO
268
+
269
+ * Rails 3 support
270
+ * Replace current_object with a block param for collection methods
271
+ * XML support
272
+
273
+ ## Acknowledgements
274
+
275
+ * [RABL](http://github.com/nesquena/rabl) - Inspiration
276
+ * [Tilt](https://github.com/rtomayko/tilt) - Mega awesome goodness
277
+
278
+ ## Copyright
279
+
280
+ Copyright (c) 2011 Alex Sharp. See the MIT-LICENSE file for full
281
+ copyright information.
data/Rakefile ADDED
@@ -0,0 +1,7 @@
1
+ # require 'bundler'
2
+ # Bundler::GemHelper.install_tasks
3
+
4
+ require 'rspec/core/rake_task'
5
+ RSpec::Core::RakeTask.new(:spec)
6
+
7
+ task :default => :spec
data/bldr.gemspec ADDED
@@ -0,0 +1,28 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "bldr/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "bldr"
7
+ s.version = Bldr::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Alex Sharp"]
10
+ s.email = ["ajsharp@gmail.com"]
11
+ s.homepage = "https://github.com/ajsharp/bldr"
12
+ s.summary = %q{Templating library with a simple, minimalist DSL.}
13
+ s.description = %q{Provides a simple and intuitive templating DSL for serializing objects to JSON.}
14
+
15
+ s.rubyforge_project = "bldr"
16
+
17
+ s.files = `git ls-files`.split("\n")
18
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
19
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
+ s.require_paths = ["lib"]
21
+
22
+ s.add_dependency 'multi_json', '~> 1.0.3'
23
+
24
+ s.add_development_dependency 'json_pure'
25
+ s.add_development_dependency 'sinatra', '~>1.2.6'
26
+ s.add_development_dependency 'tilt', '~>1.3.2'
27
+ s.add_development_dependency 'yajl-ruby'
28
+ end
@@ -0,0 +1,14 @@
1
+
2
+ module Bldr
3
+
4
+ class Engine
5
+ attr_reader :template, :options, :result
6
+
7
+ def initialize(template, options = {})
8
+ @template, @options = template, options
9
+ @result = {}
10
+ end
11
+
12
+ end
13
+
14
+ end
data/lib/bldr/node.rb ADDED
@@ -0,0 +1,171 @@
1
+
2
+ module Bldr
3
+
4
+ class Node
5
+
6
+ attr_reader :current_object, :result, :parent
7
+
8
+ # Initialize a new Node instance.
9
+ #
10
+ # @example Building a simple node
11
+ # node = Node.new do
12
+ # Node.new(:person => Person.new("alex")) do
13
+ # attributes(:name)
14
+ # end
15
+ # end
16
+ # node.to_json # => {"person": {"name": "alex"}}
17
+ #
18
+ #
19
+ # @param [Object] value an object to serialize.
20
+ def initialize(value = nil, opts = {}, &block)
21
+ @current_object = value
22
+ @parent = opts[:parent]
23
+
24
+ # Storage hash for all descendant nodes
25
+ @result = {}
26
+
27
+ instance_eval(&block) if block_given?
28
+ end
29
+
30
+ # Merge the local results into the ancestor result hash.
31
+ #
32
+ # @return [Hash]
33
+ def render!
34
+ result
35
+ end
36
+
37
+ # Return the json-encoded result hash.
38
+ #
39
+ # @return [String] the json-encoded result hash
40
+ def to_json
41
+ MultiJson.encode(result)
42
+ end
43
+
44
+ # Create and render a node.
45
+ #
46
+ # @example A keyed object
47
+ # get '/users/:id' do
48
+ # user = User.find(params['id'])
49
+ #
50
+ # bldr :'users/show.json', :locals => {:user => user}
51
+ # end
52
+ #
53
+ # # views/users/show.json.bldr
54
+ # object :user => user do
55
+ # attributes :name, :email
56
+ #
57
+ # attribute(:id) { |person| person.id.to_s }
58
+ # end
59
+ #
60
+ # @example Root-level object with no key
61
+ # get '/' do
62
+ # url = "http://google.com"
63
+ #
64
+ # bldr :'template.json', :locals => {:url => url}
65
+ # end
66
+ #
67
+ # # views/template.json.bldr
68
+ # object do
69
+ # attributes(:url) { url }
70
+ # end
71
+ #
72
+ # @param [Hash, Nil] hash a key/value pair indicating the output key name
73
+ # and the object to serialize.
74
+ # @param [Proc] block the code block to evaluate
75
+ #
76
+ # @return [String] returns a json-encoded string of itself and all
77
+ # descendant nodes.
78
+ def object(hash = nil, &block)
79
+ if hash
80
+ key = hash.keys.first
81
+ value = hash.values.first
82
+ end
83
+
84
+ node = Node.new(value, :parent => self, &block)
85
+ merge_result!(key, node.render!)
86
+ node.parent.to_json
87
+ end
88
+
89
+ def collection(items, &block)
90
+ key = items.keys.first
91
+ items = items.values.first
92
+
93
+ merge_result!(key, [])
94
+ items.each do |item|
95
+ node = Node.new(item, :parent => self, &block)
96
+ append_result!(key, node.render!)
97
+ end
98
+ end
99
+
100
+ # Add attributes to the result hash in a variety of ways
101
+ #
102
+ # @example Simple list of attributes
103
+ # object :person => dude do
104
+ # attributes :name, :age
105
+ # end
106
+ #
107
+ # @example Attribute aliasing
108
+ # object :person => dude do
109
+ # attributes :surname => :last_name
110
+ # end
111
+ #
112
+ # @example Dynamic attributes (explicit object context)
113
+ # object :person => employee do
114
+ # collection :colleagues => employee.colleagues do |colleague|
115
+ # attribute :isBoss do |colleague|
116
+ # employee.works_with?(colleague) && colleague.admin?
117
+ # end
118
+ # end
119
+ # end
120
+ #
121
+ # @example Dynamic attributes (implicit object context)
122
+ # object :person => dude do
123
+ # collection :colleagues => employee.colleagues do |colleague|
124
+ # attribute :rank do
125
+ # # method called on colleague
126
+ # if admin? && superior_to?(employee)
127
+ # "High Up"
128
+ # end
129
+ # end
130
+ # end
131
+ # end
132
+ #
133
+ # @return [Nil]
134
+ def attributes(*args, &block)
135
+ if block_given?
136
+ if args.size > 1
137
+ raise(ArgumentError, "You may only pass one argument to #attribute when using the block syntax.")
138
+ end
139
+
140
+ merge_result!(args.first, (block.arity == 1) ? block.call(current_object) : current_object.instance_eval(&block))
141
+ return nil
142
+ end
143
+
144
+ args.each do |arg|
145
+ if arg.is_a?(Hash)
146
+ merge_result!(arg.keys.first, current_object.send(arg.values.first))
147
+ else
148
+ merge_result!(arg, current_object.send(arg))
149
+ end
150
+ end
151
+ nil
152
+ end
153
+ alias :attribute :attributes
154
+
155
+ private
156
+
157
+ # Merges values into the "local" result hash.
158
+ def merge_result!(key, val)
159
+ if key
160
+ result[key] = val
161
+ else
162
+ result.merge!(val)
163
+ end
164
+ end
165
+
166
+ def append_result!(key, val)
167
+ result[key] << val
168
+ end
169
+
170
+ end
171
+ end
@@ -0,0 +1,28 @@
1
+ require 'tilt'
2
+
3
+ module Bldr
4
+
5
+ class Template < Tilt::Template
6
+ # attr_reader :engine
7
+ # self.default_mime_type = 'application/json'
8
+
9
+ def initialize_engine
10
+ require_template_library 'bldr'
11
+ end
12
+
13
+ def self.engine_initialized?
14
+ defined? ::Bldr
15
+ end
16
+
17
+ def prepare
18
+ @engine = Bldr::Engine.new(data, options)
19
+ end
20
+
21
+ def precompiled_template(locals)
22
+ data.to_s
23
+ end
24
+
25
+ end
26
+
27
+ Tilt.register 'bldr', Bldr::Template
28
+ end
@@ -0,0 +1,4 @@
1
+
2
+ module Bldr
3
+ VERSION = '0.1.0'
4
+ end
data/lib/bldr.rb ADDED
@@ -0,0 +1,15 @@
1
+
2
+ $:.unshift(File.dirname(File.expand_path(__FILE__)))
3
+
4
+ require 'multi_json'
5
+ require 'bldr/engine'
6
+ require 'bldr/template'
7
+ require 'bldr/node'
8
+
9
+ module Bldr
10
+ class << self
11
+ def json_encoder=(encoder)
12
+ MultiJson.engine = encoder
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,29 @@
1
+ require 'sinatra/base'
2
+
3
+ module Sinatra
4
+
5
+ module Bldr
6
+ module Helpers
7
+
8
+ # @param [String, Symbol] template the template to render
9
+ # Can be a relative file location or a string template.
10
+ # The template may also be passed in as the block argument
11
+ # to this method, in which case, template argument is nil.
12
+ #
13
+ # @param [Hash] opts a hash of options
14
+ # @option opts [Hash] :locals a hash of local variables to be used in the template
15
+ # @option
16
+ def bldr(template, opts = {}, &block)
17
+ opts[:scope] = ::Bldr::Node.new
18
+ locals = opts.delete(:locals)
19
+ render(:bldr, template, opts, locals, &block)
20
+ end
21
+ end
22
+
23
+ def self.registered(app)
24
+ app.helpers Helpers
25
+ end
26
+ end
27
+
28
+ register Bldr
29
+ end
@@ -0,0 +1,11 @@
1
+
2
+ object :person => bert do
3
+ attributes :name, :age
4
+ attribute :name_age do
5
+ "#{name} #{age}"
6
+ end
7
+
8
+ object :friend => ernie do
9
+ attributes :name, :age
10
+ end
11
+ end
@@ -0,0 +1,5 @@
1
+
2
+ object do
3
+ attribute(:name) { name }
4
+ attribute(:age) { age }
5
+ end
@@ -0,0 +1,33 @@
1
+ require 'spec_helper'
2
+
3
+ describe "evaluating a tilt template" do
4
+ it "registers with Tilt" do
5
+ Tilt['test.bldr'].should == Bldr::Template
6
+ end
7
+
8
+ it "renders a template" do
9
+ alex = Person.new
10
+ alex.name = 'alex'
11
+
12
+ tpl = Bldr::Template.new { "object(:person => alex) { attribute(:name) }" }
13
+ tpl.render(Bldr::Node.new, :alex => alex).should == jsonify({:person => {:name => 'alex'}})
14
+ end
15
+
16
+ it "works when render two top-level objects" do
17
+ alex = Person.new('alex')
18
+ john = Person.new('john')
19
+
20
+ tpl = Bldr::Template.new {
21
+ <<-RUBY
22
+ object(:person_1 => alex) { attribute(:name) }
23
+ object(:person_2 => john) { attribute(:name) }
24
+ RUBY
25
+ }
26
+
27
+ result = tpl.render(Bldr::Node.new, :alex => alex, :john => john)
28
+ result.should == jsonify({
29
+ :person_1 => {:name => 'alex'},
30
+ :person_2 => {:name => 'john'}
31
+ })
32
+ end
33
+ end
@@ -0,0 +1,88 @@
1
+ require 'spec_helper'
2
+
3
+
4
+ describe "Using Bldr with a sinatra app" do
5
+ require 'sinatra/bldr'
6
+
7
+ class TestApp < Sinatra::Base
8
+ register Sinatra::Bldr
9
+
10
+ set :views, File.expand_path(__FILE__ + '/../..')
11
+
12
+ get '/' do
13
+ alex = Person.new("alex", 25)
14
+ tpl = <<-RUBY
15
+ object(:dude => alex) do
16
+ attributes :name, :age
17
+ end
18
+ RUBY
19
+
20
+ status(200)
21
+ bldr tpl, :locals => {:alex => alex}
22
+ end
23
+
24
+ get '/collections' do
25
+ alex = Person.new('alex', 25)
26
+
27
+ tpl = <<-RUBY
28
+ object :person => alex do
29
+ attributes :name, :age
30
+
31
+ collection :friends => [Person.new("john", 24)] do
32
+ attributes :name, :age
33
+ end
34
+ end
35
+ RUBY
36
+
37
+ bldr tpl, :locals => {:alex => alex}
38
+ end
39
+
40
+ get '/template' do
41
+ bert = Person.new('bert', 25)
42
+ ernie = Person.new('ernie', 26)
43
+ bldr :'fixtures/nested_objects.json', :locals => {:bert => bert, :ernie => ernie}
44
+ end
45
+
46
+ get '/root_template' do
47
+ name = "john doe"
48
+ age = 26
49
+
50
+ bldr :'fixtures/root_template.json', :locals => {:name => name, :age => age}
51
+ end
52
+ end
53
+
54
+ it "returns json for a simple single-level template" do
55
+ request = Rack::MockRequest.new(TestApp)
56
+ response = request.get '/'
57
+ response.status.should == 200
58
+ parse_json(response.body).should == {'dude' => {'name' => 'alex', 'age' => 25}}
59
+ end
60
+
61
+ it "properly serializes templates with collections" do
62
+ request = Rack::MockRequest.new(TestApp)
63
+ response = request.get '/collections'
64
+
65
+ response.status.should == 200
66
+ parse_json(response.body).should == {
67
+ 'person'=> {'name' => 'alex', 'age' => 25, 'friends' => [{'name' => 'john', 'age' => 24}]}
68
+ }
69
+ end
70
+
71
+ it "works with template files" do
72
+ request = Rack::MockRequest.new(TestApp)
73
+ response = request.get '/template'
74
+
75
+ parse_json(response.body).should == {
76
+ 'person' => {'name' => 'bert', 'age' => 25, 'name_age' => "bert 25",
77
+ 'friend' => {'name' => 'ernie', 'age' => 26}
78
+ }
79
+ }
80
+ end
81
+
82
+ it "allows using root-level attributes" do
83
+ request = Rack::MockRequest.new(TestApp)
84
+ response = request.get '/root_template'
85
+
86
+ parse_json(response.body).should == {'name' => 'john doe', 'age' => 26}
87
+ end
88
+ end
@@ -0,0 +1,8 @@
1
+
2
+ class Comment
3
+ attr_accessor :body, :name, :email
4
+
5
+ def initialize(body = nil, name = nil, email = nil)
6
+ @body, @name, @email = body, name, email
7
+ end
8
+ end
@@ -0,0 +1,9 @@
1
+
2
+ class Person
3
+
4
+ attr_accessor :name, :age
5
+
6
+ def initialize(name = nil, age = nil)
7
+ @name, @age = name, age
8
+ end
9
+ end
@@ -0,0 +1,10 @@
1
+
2
+ class Post
3
+ attr_accessor :title, :body, :comments
4
+
5
+ def initialize(title = nil, body = nil)
6
+ @title, @body = title, body
7
+ @comments = []
8
+ end
9
+
10
+ end
@@ -0,0 +1,27 @@
1
+ require 'rubygems'
2
+ require 'rspec'
3
+
4
+ require 'yajl'
5
+ require 'tilt'
6
+
7
+ $:.unshift(File.dirname(File.expand_path(__FILE__)))
8
+
9
+ require File.join(File.dirname(File.expand_path(__FILE__)), "..", "lib", "bldr")
10
+
11
+ Dir['spec/models/*'].each { |f| require File.expand_path(f) }
12
+
13
+ RSpec.configure do |c|
14
+ def node_wrap(*args, &block)
15
+ Bldr::Node.new(*args, &block)
16
+ end
17
+
18
+ # Parse some json and return a ruby object
19
+ def parse_json(str)
20
+ Yajl::Parser.parse(str)
21
+ end
22
+
23
+ # Jsonify a ruby object
24
+ def jsonify(hash)
25
+ Yajl::Encoder.encode(hash)
26
+ end
27
+ end
@@ -0,0 +1,17 @@
1
+ require 'spec_helper'
2
+
3
+ describe "the json encoding library" do
4
+ it "uses yajl by default" do
5
+ MultiJson.engine.should == MultiJson::Engines::Yajl
6
+ end
7
+
8
+ it "allows changing the json encoder to json pure" do
9
+ Bldr.json_encoder = :json_pure
10
+ MultiJson.engine.should == MultiJson::Engines::JsonPure
11
+ end
12
+
13
+ it "allows changing the json encoder to the json gem" do
14
+ Bldr.json_encoder = :json_gem
15
+ MultiJson.engine.should == MultiJson::Engines::JsonGem
16
+ end
17
+ end
@@ -0,0 +1,275 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Node#attributes" do
4
+ def wrap(&block)
5
+ alex = Person.new('alex').tap { |p| p.age = 25; p }
6
+ Bldr::Node.new do
7
+ object(:person => alex, &block)
8
+ end
9
+ end
10
+
11
+ it "adds attributes of the person to the result hash" do
12
+ node = wrap { attributes(:name, :age) }
13
+ node.render!.should == {:person => {:name => 'alex', :age => 25}}
14
+ end
15
+
16
+ it "supports dynamic block attributes with explicit object context" do
17
+ node = wrap do
18
+ attribute(:oldness) do |person|
19
+ "#{person.age} years"
20
+ end
21
+ end
22
+
23
+ node.render!.should == {:person => {:oldness => "25 years"}}
24
+ end
25
+
26
+ it "supports dynamic block attributes with implicit object context" do
27
+ node = wrap do
28
+ attribute(:oldness) do
29
+ "#{age} years"
30
+ end
31
+ end
32
+
33
+ node.render!.should == {:person => {:oldness => "25 years"}}
34
+ end
35
+
36
+ it "raises an error when you use the block syntax with more than one attribute" do
37
+ expect {
38
+ node_wrap {
39
+ attributes(:one, :two) do |person|
40
+ "..."
41
+ end
42
+ }
43
+ }.to raise_error(ArgumentError, "You may only pass one argument to #attribute when using the block syntax.")
44
+ end
45
+
46
+ it "returns nil attributes in the result" do
47
+ node = node_wrap do
48
+ object :person => Person.new('alex') do
49
+ attributes :name, :age
50
+ end
51
+ end
52
+
53
+ node.render!.should == {:person => {:name => 'alex', :age => nil}}
54
+ end
55
+
56
+ end
57
+
58
+ describe "Node#render!" do
59
+ it "returns an empty hash when not passed an object" do
60
+ Bldr::Node.new.render!.should == {}
61
+ end
62
+
63
+ it "a document with a single node with no nesting" do
64
+ node = node_wrap do
65
+ object :person => Person.new('alex') do
66
+ attributes :name
67
+ end
68
+ end
69
+
70
+ node.render!.should == {:person => {:name => 'alex'}}
71
+ end
72
+
73
+ it "works for multiple top-level objects" do
74
+ alex, john = Person.new("alex"), Person.new("john")
75
+
76
+ node = node_wrap do
77
+ object(:alex => alex) do
78
+ attributes :name
79
+ end
80
+
81
+ object(:john => john) do
82
+ attributes :name
83
+ end
84
+ end
85
+
86
+ node.render!.should == {:alex => {:name => 'alex'}, :john => {:name => 'john'}}
87
+ end
88
+
89
+ it "recursively renders nested objects" do
90
+ node = node_wrap do
91
+ object :alex => Person.new("alex") do
92
+ attributes :name
93
+
94
+ object :friend => Person.new("john") do
95
+ attributes :name
96
+ end
97
+ end
98
+ end
99
+
100
+ node.render!.should == {
101
+ :alex => {
102
+ :name => 'alex',
103
+ :friend => {:name => 'john'}
104
+ }
105
+ }
106
+ end
107
+
108
+ describe "#attributes syntax" do
109
+ it "allows a hash to be sent where the keys are the result keys" do
110
+ alex = Person.new("alex").tap do |p|
111
+ p.age = 25
112
+ p
113
+ end
114
+
115
+ node = node_wrap do
116
+ object(:person => alex) do
117
+ attributes({:surname => :name}, :age)
118
+ end
119
+ end
120
+
121
+ node.render!.should == {:person => {:surname => 'alex', :age => 25}}
122
+ end
123
+ end
124
+ end
125
+
126
+ describe "Node#object" do
127
+ it "evaluates the block and returns json" do
128
+ node = Bldr::Node.new
129
+ result = node.object(:dude => Person.new("alex")) do
130
+ attributes :name
131
+
132
+ object(:bro => Person.new("john")) do
133
+ attributes :name
134
+ end
135
+ end
136
+
137
+ result.should == jsonify({
138
+ :dude => {:name => 'alex', :bro => {:name => 'john'}}
139
+ })
140
+ end
141
+ end
142
+
143
+ describe "Node#to_json" do
144
+ it "recursively returns the result json" do
145
+ node = node_wrap do
146
+ object :person => Person.new("alex") do
147
+ attributes :name
148
+
149
+ object :friend => Person.new("pete", 30) do
150
+ attributes :name, :age
151
+ end
152
+ end
153
+ end
154
+
155
+ node.to_json.should == jsonify({
156
+ :person => {
157
+ :name => 'alex',
158
+ :friend => {:name => 'pete', :age => 30}
159
+ }
160
+ })
161
+ end
162
+
163
+ it "returns null values for nil attributes" do
164
+ node = node_wrap do
165
+ object :person => Person.new('alex') do
166
+ attributes :name, :age
167
+ end
168
+ end
169
+
170
+ parse_json(node.to_json)['person'].should have_key('age')
171
+ parse_json(node.to_json)['person']['age'].should be_nil
172
+ end
173
+ end
174
+
175
+ describe "Node#collection" do
176
+ it "iterates through the collection and renders them as nodes" do
177
+ node = node_wrap do
178
+ object :person => Person.new('alex', 26) do
179
+ attributes :name, :age
180
+
181
+ collection :friends => [Person.new('john', 24), Person.new('jeff', 25)] do
182
+ attributes :name, :age
183
+ end
184
+ end
185
+ end
186
+
187
+ node.render!.should == {
188
+ :person => {
189
+ :name => 'alex', :age => 26,
190
+ :friends => [{:name => 'john', :age => 24}, {:name => 'jeff', :age => 25}]
191
+ }
192
+ }
193
+ end
194
+
195
+ it "renders properly when a collection is the root node" do
196
+ nodes = node_wrap do
197
+ collection :people => [Person.new('bert'), Person.new('ernie')] do
198
+ attributes :name
199
+ end
200
+ end
201
+
202
+ nodes.render!.should == {:people => [{:name => 'bert'}, {:name => 'ernie'}]}
203
+ end
204
+
205
+ it "renders nested collections properly" do
206
+ post = Post.new("my post")
207
+ post.comments << Comment.new('my comment')
208
+
209
+ nodes = node_wrap do
210
+ collection :posts => [post] do
211
+ attributes :title
212
+ attribute(:comment_count) { |post| post.comments.count }
213
+
214
+ collection :comments => current_object.comments do
215
+ attributes :body
216
+ end
217
+ end
218
+ end
219
+
220
+ nodes.render!.should == {
221
+ :posts => [
222
+ {:title => 'my post', :comment_count => 1, :comments => [{:body => 'my comment'}]}
223
+ ]
224
+ }
225
+ end
226
+
227
+ it "renders nested collections with dynamic property values correctly" do
228
+ post1 = Post.new("post 1")
229
+ post2 = Post.new("post 2")
230
+ post1.comments << Comment.new('post 1 comment')
231
+ post2.comments << Comment.new('post 2 first comment')
232
+ post2.comments << Comment.new('post 2 second comment')
233
+
234
+ nodes = node_wrap do
235
+ collection :posts => [post1, post2] do
236
+ attributes :title
237
+ attribute(:comment_count) { |post| post.comments.count }
238
+
239
+ collection :comments => current_object.comments do
240
+ attributes :body
241
+ end
242
+ end
243
+ end
244
+
245
+ nodes.render!.should == {
246
+ :posts => [
247
+ {
248
+ :title => 'post 1',
249
+ :comment_count => 1,
250
+ :comments => [{:body => 'post 1 comment'}]
251
+ },
252
+ {
253
+ :title => 'post 2',
254
+ :comment_count => 2,
255
+ :comments => [{:body => 'post 2 first comment'}, {:body => 'post 2 second comment'}]
256
+ }
257
+ ]
258
+ }
259
+ end
260
+
261
+ it "allows root level attributes using local variables" do
262
+ node = node_wrap do
263
+ name = "john doe"
264
+ age = 25
265
+
266
+ object do
267
+ attribute(:name) { name }
268
+ attribute(:age) { age }
269
+ end
270
+ end
271
+
272
+ node.render!.should == {:name => 'john doe', :age => 25}
273
+ end
274
+
275
+ end
metadata ADDED
@@ -0,0 +1,143 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: bldr
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: 0.1.0
6
+ platform: ruby
7
+ authors:
8
+ - Alex Sharp
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2011-08-04 00:00:00 -07:00
14
+ default_executable:
15
+ dependencies:
16
+ - !ruby/object:Gem::Dependency
17
+ name: multi_json
18
+ prerelease: false
19
+ requirement: &id001 !ruby/object:Gem::Requirement
20
+ none: false
21
+ requirements:
22
+ - - ~>
23
+ - !ruby/object:Gem::Version
24
+ version: 1.0.3
25
+ type: :runtime
26
+ version_requirements: *id001
27
+ - !ruby/object:Gem::Dependency
28
+ name: json_pure
29
+ prerelease: false
30
+ requirement: &id002 !ruby/object:Gem::Requirement
31
+ none: false
32
+ requirements:
33
+ - - ">="
34
+ - !ruby/object:Gem::Version
35
+ version: "0"
36
+ type: :development
37
+ version_requirements: *id002
38
+ - !ruby/object:Gem::Dependency
39
+ name: sinatra
40
+ prerelease: false
41
+ requirement: &id003 !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - ~>
45
+ - !ruby/object:Gem::Version
46
+ version: 1.2.6
47
+ type: :development
48
+ version_requirements: *id003
49
+ - !ruby/object:Gem::Dependency
50
+ name: tilt
51
+ prerelease: false
52
+ requirement: &id004 !ruby/object:Gem::Requirement
53
+ none: false
54
+ requirements:
55
+ - - ~>
56
+ - !ruby/object:Gem::Version
57
+ version: 1.3.2
58
+ type: :development
59
+ version_requirements: *id004
60
+ - !ruby/object:Gem::Dependency
61
+ name: yajl-ruby
62
+ prerelease: false
63
+ requirement: &id005 !ruby/object:Gem::Requirement
64
+ none: false
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: "0"
69
+ type: :development
70
+ version_requirements: *id005
71
+ description: Provides a simple and intuitive templating DSL for serializing objects to JSON.
72
+ email:
73
+ - ajsharp@gmail.com
74
+ executables: []
75
+
76
+ extensions: []
77
+
78
+ extra_rdoc_files: []
79
+
80
+ files:
81
+ - .gitignore
82
+ - .rvmrc
83
+ - .travis.yml
84
+ - Gemfile
85
+ - MIT-LICENSE
86
+ - README.md
87
+ - Rakefile
88
+ - bldr.gemspec
89
+ - lib/bldr.rb
90
+ - lib/bldr/engine.rb
91
+ - lib/bldr/node.rb
92
+ - lib/bldr/template.rb
93
+ - lib/bldr/version.rb
94
+ - lib/sinatra/bldr.rb
95
+ - spec/fixtures/nested_objects.json.bldr
96
+ - spec/fixtures/root_template.json.bldr
97
+ - spec/functional/tilt_template_spec.rb
98
+ - spec/integration/sinatra_spec.rb
99
+ - spec/models/comment.rb
100
+ - spec/models/person.rb
101
+ - spec/models/post.rb
102
+ - spec/spec_helper.rb
103
+ - spec/unit/bldr_spec.rb
104
+ - spec/unit/node_spec.rb
105
+ has_rdoc: true
106
+ homepage: https://github.com/ajsharp/bldr
107
+ licenses: []
108
+
109
+ post_install_message:
110
+ rdoc_options: []
111
+
112
+ require_paths:
113
+ - lib
114
+ required_ruby_version: !ruby/object:Gem::Requirement
115
+ none: false
116
+ requirements:
117
+ - - ">="
118
+ - !ruby/object:Gem::Version
119
+ version: "0"
120
+ required_rubygems_version: !ruby/object:Gem::Requirement
121
+ none: false
122
+ requirements:
123
+ - - ">="
124
+ - !ruby/object:Gem::Version
125
+ version: "0"
126
+ requirements: []
127
+
128
+ rubyforge_project: bldr
129
+ rubygems_version: 1.6.2
130
+ signing_key:
131
+ specification_version: 3
132
+ summary: Templating library with a simple, minimalist DSL.
133
+ test_files:
134
+ - spec/fixtures/nested_objects.json.bldr
135
+ - spec/fixtures/root_template.json.bldr
136
+ - spec/functional/tilt_template_spec.rb
137
+ - spec/integration/sinatra_spec.rb
138
+ - spec/models/comment.rb
139
+ - spec/models/person.rb
140
+ - spec/models/post.rb
141
+ - spec/spec_helper.rb
142
+ - spec/unit/bldr_spec.rb
143
+ - spec/unit/node_spec.rb