boxer 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +5 -0
- data/Gemfile +3 -0
- data/LICENSE +21 -0
- data/README.md +106 -0
- data/Rakefile +1 -0
- data/boxer.gemspec +27 -0
- data/lib/boxer.rb +101 -0
- data/lib/boxer/version.rb +3 -0
- data/spec/boxer_spec.rb +206 -0
- data/spec/spec_helper.rb +5 -0
- metadata +82 -0
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2011 Brad Fults, Gowalla Incorporated
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
# Boxer
|
2
|
+
|
3
|
+
Boxer is a template engine for creating nested and multi-view JSON objects
|
4
|
+
from Ruby hashes.
|
5
|
+
|
6
|
+
## The Problem
|
7
|
+
|
8
|
+
Say you have a couple ActiveRecord models in your Rails app and you want to
|
9
|
+
render an API response in JSON, but the view of each of those model objects
|
10
|
+
may change based on the API action that's being requested.
|
11
|
+
|
12
|
+
* User
|
13
|
+
* Place
|
14
|
+
|
15
|
+
For instance, the API for `GET /users/:id` should render a full representation
|
16
|
+
of the User object in question, including all relevant attributes.
|
17
|
+
|
18
|
+
But in your `GET /places/:id/users` API call, you only need short-form
|
19
|
+
representations of the users at that place, without every single attribute
|
20
|
+
being included in the response.
|
21
|
+
|
22
|
+
## The Solution
|
23
|
+
|
24
|
+
Boxer allows you to define a box for each type of object you'd like to display
|
25
|
+
(or for each amalgamation of objects you want to display—it's up to you).
|
26
|
+
|
27
|
+
Boxer.box(:user) do |box, user|
|
28
|
+
{
|
29
|
+
:name => user.name,
|
30
|
+
:age => user.age,
|
31
|
+
}
|
32
|
+
end
|
33
|
+
|
34
|
+
To display different views on the same object, you can use Boxer's views:
|
35
|
+
|
36
|
+
Boxer.box(:user) do |box, user|
|
37
|
+
box.view(:base) do
|
38
|
+
{
|
39
|
+
:name => user.name,
|
40
|
+
:age => user.age,
|
41
|
+
}
|
42
|
+
end
|
43
|
+
|
44
|
+
box.view(:full, :extends => :base) do
|
45
|
+
{
|
46
|
+
:email => user.email,
|
47
|
+
:is_private => user.private?,
|
48
|
+
}
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
As you might guess, the `:full` view includes all attributes in the `:base`
|
53
|
+
view by virtue of the `:extends` option.
|
54
|
+
|
55
|
+
Now, in order to render a User with the `:base` view, simple call `Boxer.ship`:
|
56
|
+
|
57
|
+
Boxer.ship(:user, User.first)
|
58
|
+
|
59
|
+
Boxer assumes that you want the `:base` view if no view is specified to
|
60
|
+
`ship`—it's the only specially-named view.
|
61
|
+
|
62
|
+
To render the full view for the same user:
|
63
|
+
|
64
|
+
Boxer.ship(:user, User.first, :view => :full)
|
65
|
+
|
66
|
+
Which will give you back a Ruby hash on which you can call `#to_json`, to render
|
67
|
+
your JSON response<sup>1</sup>:
|
68
|
+
|
69
|
+
>> Boxer.ship(:user, User.first, :view => :full).to_json
|
70
|
+
=> "{"name": "Bo Jim", "age": 17, "email": "b@a.com", "is_private": false}"
|
71
|
+
|
72
|
+
Composing different boxes together is as simple as calling `Boxer.ship` from
|
73
|
+
within a box—it's just Ruby:
|
74
|
+
|
75
|
+
Boxer.box(:place) do |box, place|
|
76
|
+
{
|
77
|
+
:name => place.name,
|
78
|
+
:address => place.address,
|
79
|
+
:top_user => Boxer.ship(:user, place.users.order(:visits).first),
|
80
|
+
}
|
81
|
+
end
|
82
|
+
|
83
|
+
1. `Hash#to_json` requires the [`json` library](http://rubygems.org/gems/json)
|
84
|
+
|
85
|
+
## More Features
|
86
|
+
|
87
|
+
See [the wiki](/h3h/boxer/wiki) for more features of Boxer, including:
|
88
|
+
|
89
|
+
* [Extra Arguments](/h3h/boxer/wiki/Extra-Arguments)
|
90
|
+
* [Preconditions](/h3h/boxer/wiki/Preconditions)
|
91
|
+
* [Helper Methods in Boxes](/h3h/boxer/wiki/Helper-Methods-in-Boxes)
|
92
|
+
* [Box Includes](/h3h/boxer/wiki/Box-Includes)
|
93
|
+
* [Multiple Inheritance](/h3h/boxer/wiki/Multiple-Inheritance)
|
94
|
+
|
95
|
+
## Installation
|
96
|
+
|
97
|
+
Install the [boxer gem](http://rubygems.org/gems/boxer).
|
98
|
+
|
99
|
+
## Original Author
|
100
|
+
|
101
|
+
* [Brad Fults](http://h3h.net/), Gowalla Incorporated
|
102
|
+
|
103
|
+
## Inspiration
|
104
|
+
|
105
|
+
Boxer was inspired by [rabl](https://github.com/nesquena/rabl),
|
106
|
+
by Nathan Esquenazi.
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
data/boxer.gemspec
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "boxer/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = 'boxer'
|
7
|
+
s.version = Boxer::VERSION
|
8
|
+
s.authors = ['Brad Fults']
|
9
|
+
s.email = ['bfults@gmail.com']
|
10
|
+
s.homepage = 'http://github.com/h3h/boxer'
|
11
|
+
s.license = 'MIT'
|
12
|
+
s.summary = %q{
|
13
|
+
Easy custom-defined templates for JSON generation of objects in Ruby.
|
14
|
+
}
|
15
|
+
s.description = %q{
|
16
|
+
A composable templating system for generating JSON via Ruby hashes, with
|
17
|
+
different possible views on each object and runtime data passing.
|
18
|
+
}
|
19
|
+
|
20
|
+
s.files = `git ls-files`.split("\n")
|
21
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
22
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
23
|
+
s.require_paths = ['lib']
|
24
|
+
|
25
|
+
s.add_development_dependency 'rspec', '>= 2.0.0'
|
26
|
+
s.add_runtime_dependency 'activesupport', '>= 3.0.0'
|
27
|
+
end
|
data/lib/boxer.rb
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
require 'boxer/version'
|
2
|
+
require 'active_support/core_ext/class/attribute_accessors'
|
3
|
+
require 'active_support/core_ext/array/extract_options'
|
4
|
+
require 'active_support/core_ext/hash/deep_merge'
|
5
|
+
require 'ostruct'
|
6
|
+
|
7
|
+
class Boxer
|
8
|
+
cattr_accessor :config
|
9
|
+
|
10
|
+
self.config = OpenStruct.new({
|
11
|
+
:box_includes => [],
|
12
|
+
})
|
13
|
+
|
14
|
+
class ViewMissingError < StandardError; end
|
15
|
+
|
16
|
+
def initialize(name, options={}, &block)
|
17
|
+
@name = name
|
18
|
+
@block = block
|
19
|
+
@fallback = []
|
20
|
+
@views = {}
|
21
|
+
@views_chain = {}
|
22
|
+
@options = options
|
23
|
+
end
|
24
|
+
|
25
|
+
## class methods
|
26
|
+
|
27
|
+
def self.box(name, options={}, &block)
|
28
|
+
(@boxes ||= {})[name] = self.new(name, options, &block)
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.boxes
|
32
|
+
@boxes
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.clear!
|
36
|
+
@boxes = {}
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.configure
|
40
|
+
yield config
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.ship(name, *args)
|
44
|
+
fail "Unknown box: #{name.inspect}" unless @boxes.has_key?(name)
|
45
|
+
@boxes[name].ship(*args)
|
46
|
+
end
|
47
|
+
|
48
|
+
## instance methods
|
49
|
+
|
50
|
+
def emit(val)
|
51
|
+
@fallback = [val]
|
52
|
+
end
|
53
|
+
|
54
|
+
def ship(*args)
|
55
|
+
args = args.dup
|
56
|
+
if args.last.is_a?(Hash)
|
57
|
+
args[-1] = args.last.dup
|
58
|
+
view = args.last.delete(:view)
|
59
|
+
args.slice!(-1) if args.last.empty?
|
60
|
+
end
|
61
|
+
view ||= :base
|
62
|
+
|
63
|
+
modules = self.class.config.box_includes
|
64
|
+
black_box = Class.new do
|
65
|
+
modules.each do |mod|
|
66
|
+
include mod
|
67
|
+
end
|
68
|
+
end
|
69
|
+
block_result = black_box.new.instance_exec(self, *args, &@block)
|
70
|
+
|
71
|
+
if @fallback.length > 0
|
72
|
+
return @fallback.pop
|
73
|
+
elsif @views_chain.any?
|
74
|
+
unless @views_chain.has_key?(view)
|
75
|
+
fail ViewMissingError.new([@name, view].map(&:inspect).join('/'))
|
76
|
+
end
|
77
|
+
return @views_chain[view].inject({}) do |res, view_name|
|
78
|
+
res.deep_merge(@views[view_name].call(*args))
|
79
|
+
end
|
80
|
+
else
|
81
|
+
return block_result
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def precondition
|
86
|
+
yield self
|
87
|
+
end
|
88
|
+
|
89
|
+
def view(name, opts={}, &block)
|
90
|
+
@views_chain[name] = []
|
91
|
+
if opts.has_key?(:extends)
|
92
|
+
ancestors = Array(opts[:extends]).map do |parent|
|
93
|
+
(@views_chain[parent] || []) + [parent]
|
94
|
+
end.flatten.uniq
|
95
|
+
@views_chain[name] += ancestors
|
96
|
+
end
|
97
|
+
@views_chain[name] << name
|
98
|
+
|
99
|
+
@views[name] = block
|
100
|
+
end
|
101
|
+
end
|
data/spec/boxer_spec.rb
ADDED
@@ -0,0 +1,206 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'boxer'
|
3
|
+
|
4
|
+
module MyTestModule
|
5
|
+
def my_test_method; 42 end
|
6
|
+
end
|
7
|
+
|
8
|
+
module MySecondTestModule
|
9
|
+
def my_second_test_method; 43 end
|
10
|
+
end
|
11
|
+
|
12
|
+
describe Boxer do
|
13
|
+
|
14
|
+
describe ".box" do
|
15
|
+
it "can create a box based on a simple hash" do
|
16
|
+
Boxer.box(:foo) do
|
17
|
+
{:working => true}
|
18
|
+
end
|
19
|
+
|
20
|
+
Boxer.ship(:foo).should eq({:working => true})
|
21
|
+
end
|
22
|
+
|
23
|
+
it "defaults to shipping the base view, when it exists" do
|
24
|
+
Boxer.box(:foo) do |box|
|
25
|
+
box.view(:base) { {:working => true} }
|
26
|
+
end
|
27
|
+
|
28
|
+
Boxer.ship(:foo).should eq({:working => true})
|
29
|
+
end
|
30
|
+
|
31
|
+
it "fails if views are specified, but :base is missing" do
|
32
|
+
Boxer.box(:foo) do |box|
|
33
|
+
box.view(:face) { {:working => true} }
|
34
|
+
end
|
35
|
+
|
36
|
+
expect {
|
37
|
+
Boxer.ship(:foo).should eq({:working => true})
|
38
|
+
}.to raise_exception(Boxer::ViewMissingError)
|
39
|
+
end
|
40
|
+
|
41
|
+
it "executes its block in a sandbox context, not a global one" do
|
42
|
+
Boxer.box(:foo) do |box|
|
43
|
+
self
|
44
|
+
end
|
45
|
+
|
46
|
+
context_obj = Boxer.ship(:foo)
|
47
|
+
context_obj.inspect.should_not include('RSpec')
|
48
|
+
context_obj.inspect.should include('Class')
|
49
|
+
end
|
50
|
+
|
51
|
+
it "raises a ViewMissing error if given a non-existent view" do
|
52
|
+
Boxer.box(:foo) do |box|
|
53
|
+
box.view(:face) { {:working => true} }
|
54
|
+
end
|
55
|
+
|
56
|
+
expect {
|
57
|
+
Boxer.ship(:foo).should eq({:working => true})
|
58
|
+
}.to raise_exception(Boxer::ViewMissingError)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
describe ".clear!" do
|
63
|
+
it "clears all boxes" do
|
64
|
+
Boxer.box(:foo) { {:working => true} }
|
65
|
+
Boxer.clear!
|
66
|
+
Boxer.boxes.should be_empty
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
describe ".configure" do
|
71
|
+
it "sets config.box_includes via its supplied block" do
|
72
|
+
Boxer.config.box_includes = []
|
73
|
+
Boxer.configure {|config| config.box_includes = [MyTestModule] }
|
74
|
+
Boxer.config.box_includes.should include(MyTestModule)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
describe ".ship" do
|
79
|
+
it "accepts arguments and passes them to a box for shipping" do
|
80
|
+
Boxer.box(:bar) do |box, x, y, z|
|
81
|
+
box.view(:base) { {:working => true, :stuff => [x, y, z]} }
|
82
|
+
end
|
83
|
+
|
84
|
+
Boxer.ship(:bar, 1, 2, 3).should eq(
|
85
|
+
{:working => true, :stuff => [1, 2, 3]}
|
86
|
+
)
|
87
|
+
end
|
88
|
+
|
89
|
+
it "allows a hash as the final argument" do
|
90
|
+
Boxer.box(:bar) do |box, x, y, z|
|
91
|
+
box.view(:base) { {:working => true, :stuff => [x, y, z]} }
|
92
|
+
end
|
93
|
+
|
94
|
+
Boxer.ship(:bar, 1, 2, :banana => true).should eq(
|
95
|
+
{:working => true, :stuff => [1, 2, {:banana => true}]}
|
96
|
+
)
|
97
|
+
end
|
98
|
+
|
99
|
+
it "includes modules from config.box_includes in shipping boxes" do
|
100
|
+
Boxer.config.box_includes = [MyTestModule, MySecondTestModule]
|
101
|
+
Boxer.box(:bar) do |box|
|
102
|
+
box.view(:base) { {:a => my_test_method, :b => my_second_test_method} }
|
103
|
+
end
|
104
|
+
|
105
|
+
Boxer.ship(:bar).should eq({:a => 42, :b => 43})
|
106
|
+
end
|
107
|
+
|
108
|
+
it "does not mutate passed in arguments" do
|
109
|
+
Boxer.box(:bar) do |box, num, opts|
|
110
|
+
box.view(:base) { {} }
|
111
|
+
end
|
112
|
+
|
113
|
+
hash = {:view => :base}.freeze
|
114
|
+
orig_hash = hash.dup
|
115
|
+
Boxer.ship(:bar, 1, hash)
|
116
|
+
hash.should eq(orig_hash)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
describe "#precondition" do
|
121
|
+
it "ships the value emitted in the precondition" do
|
122
|
+
Boxer.box(:foo) do |box, obj|
|
123
|
+
box.precondition {|resp| resp.emit({}) if obj.nil? }
|
124
|
+
box.view(:base) { {:working => true} }
|
125
|
+
end
|
126
|
+
|
127
|
+
Boxer.ship(:foo, nil).should eq({})
|
128
|
+
end
|
129
|
+
|
130
|
+
it "disregards the precondition if no value is emitted" do
|
131
|
+
Boxer.box(:foo) do |box, obj|
|
132
|
+
box.precondition {|resp| resp.emit({}) if obj.nil? }
|
133
|
+
box.view(:base) { {:working => true} }
|
134
|
+
end
|
135
|
+
|
136
|
+
Boxer.ship(:foo, Object.new).should eq({:working => true})
|
137
|
+
end
|
138
|
+
|
139
|
+
it "can handle nil as an emitted precondition value" do
|
140
|
+
Boxer.box(:foo) do |box, obj|
|
141
|
+
box.precondition {|resp| resp.emit(nil) if obj.nil? }
|
142
|
+
box.view(:base) { {:working => true} }
|
143
|
+
end
|
144
|
+
|
145
|
+
Boxer.ship(:foo, nil).should eq(nil)
|
146
|
+
end
|
147
|
+
|
148
|
+
it "doesn't remember a fallback value from a previous shipping" do
|
149
|
+
Boxer.box(:foo) do |box, obj|
|
150
|
+
box.precondition {|resp| resp.emit({}) if obj.nil? }
|
151
|
+
box.view(:base) { {:working => true} }
|
152
|
+
end
|
153
|
+
|
154
|
+
Boxer.ship(:foo, nil).should eq({})
|
155
|
+
Boxer.ship(:foo, Object.new).should eq({:working => true})
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
describe "#view" do
|
160
|
+
it "allow extending of other views" do
|
161
|
+
Boxer.box(:foo) do |box|
|
162
|
+
box.view(:base) { {:working => true} }
|
163
|
+
box.view(:face, :extends => :base) { {:awesome => true} }
|
164
|
+
end
|
165
|
+
|
166
|
+
Boxer.ship(:foo, :view => :face).should eq(
|
167
|
+
{:working => true, :awesome => true}
|
168
|
+
)
|
169
|
+
end
|
170
|
+
|
171
|
+
it "extends by smashing lesser (extended) views" do
|
172
|
+
Boxer.box(:foo) do |box|
|
173
|
+
box.view(:base) { {:working => true} }
|
174
|
+
box.view(:face, :extends => :base) { {:working => :awesome} }
|
175
|
+
end
|
176
|
+
|
177
|
+
Boxer.ship(:foo, :view => :face).should eq(
|
178
|
+
{:working => :awesome}
|
179
|
+
)
|
180
|
+
end
|
181
|
+
|
182
|
+
it "extends by merging nested keys without overriding" do
|
183
|
+
Boxer.box(:foo) do |box|
|
184
|
+
box.view(:base) { {:working => {:a => 1}} }
|
185
|
+
box.view(:face, :extends => :base) { {:working => {:b => 2}} }
|
186
|
+
end
|
187
|
+
|
188
|
+
Boxer.ship(:foo, :view => :face).should eq(
|
189
|
+
{:working => {:a => 1, :b => 2}}
|
190
|
+
)
|
191
|
+
end
|
192
|
+
|
193
|
+
it "allows extending in a chain of more than two views" do
|
194
|
+
Boxer.box(:foo) do |box|
|
195
|
+
box.view(:base) { {:working => {:a => 1}} }
|
196
|
+
box.view(:face, :extends => :base) { {:working => {:b => 2}} }
|
197
|
+
box.view(:race, :extends => :face) { {:working => {:c => 3}} }
|
198
|
+
end
|
199
|
+
|
200
|
+
Boxer.ship(:foo, :view => :race).should eq(
|
201
|
+
{:working => {:a => 1, :b => 2, :c => 3}}
|
202
|
+
)
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: boxer
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Brad Fults
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-10-18 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rspec
|
16
|
+
requirement: &70290064287800 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 2.0.0
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70290064287800
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: activesupport
|
27
|
+
requirement: &70290064287300 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 3.0.0
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *70290064287300
|
36
|
+
description: ! "\n A composable templating system for generating JSON via Ruby
|
37
|
+
hashes, with\n different possible views on each object and runtime data passing.\n
|
38
|
+
\ "
|
39
|
+
email:
|
40
|
+
- bfults@gmail.com
|
41
|
+
executables: []
|
42
|
+
extensions: []
|
43
|
+
extra_rdoc_files: []
|
44
|
+
files:
|
45
|
+
- .gitignore
|
46
|
+
- Gemfile
|
47
|
+
- LICENSE
|
48
|
+
- README.md
|
49
|
+
- Rakefile
|
50
|
+
- boxer.gemspec
|
51
|
+
- lib/boxer.rb
|
52
|
+
- lib/boxer/version.rb
|
53
|
+
- spec/boxer_spec.rb
|
54
|
+
- spec/spec_helper.rb
|
55
|
+
homepage: http://github.com/h3h/boxer
|
56
|
+
licenses:
|
57
|
+
- MIT
|
58
|
+
post_install_message:
|
59
|
+
rdoc_options: []
|
60
|
+
require_paths:
|
61
|
+
- lib
|
62
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
63
|
+
none: false
|
64
|
+
requirements:
|
65
|
+
- - ! '>='
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '0'
|
68
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
69
|
+
none: false
|
70
|
+
requirements:
|
71
|
+
- - ! '>='
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: '0'
|
74
|
+
requirements: []
|
75
|
+
rubyforge_project:
|
76
|
+
rubygems_version: 1.8.10
|
77
|
+
signing_key:
|
78
|
+
specification_version: 3
|
79
|
+
summary: Easy custom-defined templates for JSON generation of objects in Ruby.
|
80
|
+
test_files:
|
81
|
+
- spec/boxer_spec.rb
|
82
|
+
- spec/spec_helper.rb
|