bldr 0.1.0

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