rabl 0.0.1
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 +4 -0
- data/Gemfile +4 -0
- data/README.md +75 -0
- data/Rakefile +2 -0
- data/lib/rabl.rb +8 -0
- data/lib/rabl/builder.rb +117 -0
- data/lib/rabl/engine.rb +93 -0
- data/lib/rabl/template.rb +18 -0
- data/lib/rabl/version.rb +3 -0
- data/rabl.gemspec +21 -0
- metadata +75 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
# RABL #
|
2
|
+
|
3
|
+
RABL is a ruby templating system for Rails that takes a different approach for generating JSON and other formats. Rather than using the ActiveRecord 'to_json', I generally find myself wanting to use a more expressive and flexible system for generating my Public APIs. This is especially true when I the json doesn't match to the exact schema defined in the database.
|
4
|
+
|
5
|
+
There were a few things in particular I wanted to do easily:
|
6
|
+
|
7
|
+
* Create arbitrary nodes named based on combining data in the object
|
8
|
+
* Include nodes only if a condition is met
|
9
|
+
* Pass arguments to methods and store the result as a node
|
10
|
+
* Include partial templates to reduce code duplication
|
11
|
+
* Easily rename attributes from their name in the model
|
12
|
+
|
13
|
+
This general templating system solves all of those problems.
|
14
|
+
|
15
|
+
## Installation ##
|
16
|
+
|
17
|
+
gem install rabl
|
18
|
+
|
19
|
+
## Usage ##
|
20
|
+
|
21
|
+
Basic usage of the templater:
|
22
|
+
|
23
|
+
# app/views/users/show.json.rabl
|
24
|
+
attributes :id, :foo, :bar
|
25
|
+
|
26
|
+
This will generate a json response with the attributes specified. You can also include arbitrary code:
|
27
|
+
|
28
|
+
# app/views/users/show.json.rabl
|
29
|
+
code :full_name do |u|
|
30
|
+
u.first_name + " " + u.last_name
|
31
|
+
end
|
32
|
+
|
33
|
+
You can also add children nodes:
|
34
|
+
|
35
|
+
child @posts => :foobar do
|
36
|
+
attributes :id, :title
|
37
|
+
end
|
38
|
+
|
39
|
+
or use existing model associations:
|
40
|
+
|
41
|
+
child :posts => :foobar do
|
42
|
+
attributes :id, :title
|
43
|
+
end
|
44
|
+
|
45
|
+
You can also extend other rabl templates to reduce duplication:
|
46
|
+
|
47
|
+
# app/views/users/show.json.rabl
|
48
|
+
child @address do
|
49
|
+
extends "address/item"
|
50
|
+
end
|
51
|
+
|
52
|
+
or get access to the hash representation of another object:
|
53
|
+
|
54
|
+
code :location do
|
55
|
+
{ :place => partial("web/users/address", :object => @address) }
|
56
|
+
end
|
57
|
+
|
58
|
+
You can also append attributes to the root node:
|
59
|
+
|
60
|
+
glue @post do
|
61
|
+
attribute :id => :post_id
|
62
|
+
end
|
63
|
+
|
64
|
+
There is also the ability to extend other rabl templates with additional attributes:
|
65
|
+
|
66
|
+
extends "base"
|
67
|
+
|
68
|
+
code :release_year do |m|
|
69
|
+
date = m.release_date || m.series_start
|
70
|
+
date.try(:year)
|
71
|
+
end
|
72
|
+
|
73
|
+
## Issues ##
|
74
|
+
|
75
|
+
* I am sloppy and once again failed to unit test this. Don't use it in production until I do obviously.
|
data/Rakefile
ADDED
data/lib/rabl.rb
ADDED
data/lib/rabl/builder.rb
ADDED
@@ -0,0 +1,117 @@
|
|
1
|
+
module JRB
|
2
|
+
class Builder
|
3
|
+
# Constructs a new ejs generator based on given object and options
|
4
|
+
def initialize(object, options, &block)
|
5
|
+
@_object = object
|
6
|
+
@_result = {}
|
7
|
+
@options = options
|
8
|
+
end
|
9
|
+
|
10
|
+
# Returns a hash representation of the data object
|
11
|
+
# to_hash(:root => true)
|
12
|
+
def to_hash(options={})
|
13
|
+
# Attributes
|
14
|
+
@options[:attributes].each_pair do |attribute, name|
|
15
|
+
attribute(attribute, :as => name)
|
16
|
+
end if @options.has_key?(:attributes)
|
17
|
+
# Code
|
18
|
+
@options[:code].each_pair do |name, block|
|
19
|
+
code(name, &block)
|
20
|
+
end if @options.has_key?(:code)
|
21
|
+
# Children
|
22
|
+
@options[:child].each do |settings|
|
23
|
+
child(settings[:data], settings[:options], &settings[:block])
|
24
|
+
end if @options.has_key?(:child)
|
25
|
+
# Glues
|
26
|
+
@options[:glue].each do |settings|
|
27
|
+
glue(settings[:data], &settings[:block])
|
28
|
+
end if @options.has_key?(:glue)
|
29
|
+
# Extends
|
30
|
+
@options[:extends].each do |settings|
|
31
|
+
extends(settings[:file], settings[:options], &settings[:block])
|
32
|
+
end if @options.has_key?(:extends)
|
33
|
+
|
34
|
+
@_root_name ||= @_object.class.model_name.element
|
35
|
+
options[:root] ? { @_root_name => @_result } : @_result
|
36
|
+
end
|
37
|
+
|
38
|
+
# Indicates an attribute or method should be included in the json output
|
39
|
+
# attribute :foo, :as => "bar"
|
40
|
+
# attribute :foo => :bar
|
41
|
+
def attribute(*args)
|
42
|
+
if args.first.is_a?(Hash)
|
43
|
+
args.first.each_pair { |k,v| self.attribute(k, :as => v) }
|
44
|
+
else # array of attributes
|
45
|
+
options = args.extract_options!
|
46
|
+
args.each do |attribute|
|
47
|
+
@_result[options[:as] || attribute] = @_object.try(attribute) if @_object.respond_to?(attribute)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
alias_method :attributes, :attribute
|
52
|
+
|
53
|
+
# Creates an arbitrary code node that is included in the json output
|
54
|
+
# code(:foo) { "bar" }
|
55
|
+
def code(name, &block)
|
56
|
+
@_result[name] = block.call(@_object)
|
57
|
+
end
|
58
|
+
|
59
|
+
# Creates a child node that is included in json output
|
60
|
+
# child(@user) { attribute :full_name }
|
61
|
+
# child(@user => :person) { ... }
|
62
|
+
def child(data, options={}, &block)
|
63
|
+
return false unless data.present?
|
64
|
+
name, object = data_name(data), data_object(data)
|
65
|
+
@_result[name] = self.object_to_hash(object, &block)
|
66
|
+
end
|
67
|
+
|
68
|
+
# Glues data from a child node to the json_output
|
69
|
+
# glue(@user) { attribute :full_name => :user_full_name }
|
70
|
+
def glue(data, &block)
|
71
|
+
return false unless data.present?
|
72
|
+
object = data_object(data)
|
73
|
+
glued_attributes = self.object_to_hash(object, &block)
|
74
|
+
@_result.merge!(glued_attributes)
|
75
|
+
end
|
76
|
+
|
77
|
+
# Renders a partial hash based on another jrb template
|
78
|
+
# partial("users/show", :object => @user)
|
79
|
+
def partial(file, options={}, &block)
|
80
|
+
source = File.read(Rails.root.join("app/views/" + file + ".json.jrb"))
|
81
|
+
self.object_to_hash(options[:object], source, &block)
|
82
|
+
end
|
83
|
+
|
84
|
+
# Extends an existing jrb template with additional attributes in the block
|
85
|
+
# extends("users/show") { attribute :full_name }
|
86
|
+
def extends(file, options={}, &block)
|
87
|
+
options = options.merge!(:object => @_object)
|
88
|
+
result = partial(file, options, &block)
|
89
|
+
@_result.merge!(result)
|
90
|
+
end
|
91
|
+
|
92
|
+
protected
|
93
|
+
|
94
|
+
# Returns a hash based representation of any data object given ejs template block
|
95
|
+
# object_to_hash(@user) { attribute :full_name } => { ... }
|
96
|
+
def object_to_hash(object, source=nil, &block)
|
97
|
+
@options[:generator].object_to_hash(object, source, &block)
|
98
|
+
end
|
99
|
+
|
100
|
+
# data_object(data) => <AR Object>
|
101
|
+
# data_object(@user => :person) => @user
|
102
|
+
# data_object(:user => :person) => @_object.send(:user)
|
103
|
+
def data_object(data)
|
104
|
+
data = (data.is_a?(Hash) && data.keys.one?) ? data.keys.first : data
|
105
|
+
data.is_a?(Symbol) ? @_object.send(data) : data
|
106
|
+
end
|
107
|
+
|
108
|
+
# data_name(data) => "user"
|
109
|
+
# data_name(@user => :person) => :person
|
110
|
+
# data_name(@users) => :user
|
111
|
+
def data_name(data)
|
112
|
+
return data.values.first if data.is_a?(Hash)
|
113
|
+
return data.first.class.model_name.element.pluralize if data.first.is_a?(ActiveRecord::Base)
|
114
|
+
data.class.model_name.element
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
data/lib/rabl/engine.rb
ADDED
@@ -0,0 +1,93 @@
|
|
1
|
+
module JRB
|
2
|
+
class Engine
|
3
|
+
# Constructs a new ejs generator based on given vars, handler and declarations
|
4
|
+
def initialize(vars, handler, source_string=nil, &block)
|
5
|
+
@_vars = vars
|
6
|
+
@_handler = handler
|
7
|
+
@_options = { :handler => @_handler, :vars => @_vars, :generator => self }
|
8
|
+
self.copy_instance_variables_from(@_handler, [:@assigns, :@helpers]);
|
9
|
+
@_object = vars[:object] || instance_variable_get("@#{@_handler.controller.controller_name}")
|
10
|
+
instance_eval(source_string) if source_string.present?
|
11
|
+
instance_eval(&block) if block_given?
|
12
|
+
end
|
13
|
+
|
14
|
+
# Sets the object to be used as the data source for this template
|
15
|
+
# object(@user)
|
16
|
+
def object(data)
|
17
|
+
@_object = data
|
18
|
+
end
|
19
|
+
|
20
|
+
# Indicates an attribute or method should be included in the json output
|
21
|
+
# attribute :foo, :as => "bar"
|
22
|
+
# attribute :foo => :bar
|
23
|
+
def attribute(*args)
|
24
|
+
if args.first.is_a?(Hash)
|
25
|
+
args.first.each_pair { |k,v| self.attribute(k, :as => v) }
|
26
|
+
else # array of attributes
|
27
|
+
options = args.extract_options!
|
28
|
+
@_options[:attributes] ||= {}
|
29
|
+
args.each { |name| @_options[:attributes][name] = options[:as] || name }
|
30
|
+
end
|
31
|
+
end
|
32
|
+
alias_method :attributes, :attribute
|
33
|
+
|
34
|
+
# Creates an arbitrary code node that is included in the json output
|
35
|
+
# code(:foo) { "bar" }
|
36
|
+
def code(name, &block)
|
37
|
+
@_options[:code] ||= {}
|
38
|
+
@_options[:code][name] = block
|
39
|
+
end
|
40
|
+
|
41
|
+
# Creates a child node that is included in json output
|
42
|
+
# child(@user) { attribute :full_name }
|
43
|
+
def child(data, options={}, &block)
|
44
|
+
@_options[:child] ||= []
|
45
|
+
@_options[:child].push({ :data => data, :options => options, :block => block })
|
46
|
+
end
|
47
|
+
|
48
|
+
# Glues data from a child node to the json_output
|
49
|
+
# glue(@user) { attribute :full_name => :user_full_name }
|
50
|
+
def glue(data, &block)
|
51
|
+
@_options[:glue] ||= []
|
52
|
+
@_options[:glue].push({ :data => data, :block => block })
|
53
|
+
end
|
54
|
+
|
55
|
+
# Extends an existing jrb template with additional attributes in the block
|
56
|
+
# extends("users/show", :object => @user) { attribute :full_name }
|
57
|
+
def extends(file, options={}, &block)
|
58
|
+
@_options[:extends] ||= []
|
59
|
+
@_options[:extends].push({ :file => file, :options => options, :block => block })
|
60
|
+
end
|
61
|
+
|
62
|
+
# Renders a partial hash based on another jrb template
|
63
|
+
# partial("users/show", :object => @user)
|
64
|
+
def partial(file, options={}, &block)
|
65
|
+
source = File.read(Rails.root.join("app/views/" + file + ".json.jrb"))
|
66
|
+
self.object_to_hash(options[:object], source, &block)
|
67
|
+
end
|
68
|
+
|
69
|
+
# Returns a hash representation of the data object
|
70
|
+
# to_hash(:root => true)
|
71
|
+
def to_hash(options={})
|
72
|
+
if @_object.is_a?(ActiveRecord::Base)
|
73
|
+
JRB::Builder.new(@_object, @_options).to_hash(options)
|
74
|
+
elsif @_object.respond_to?(:each)
|
75
|
+
@_object.map { |object| JRB::Builder.new(object, @_options).to_hash(options) }
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# Returns a json representation of the data object
|
80
|
+
# to_json(:root => true)
|
81
|
+
def to_json(options={})
|
82
|
+
options.reverse_merge!(:root => true)
|
83
|
+
to_hash(options).to_json
|
84
|
+
end
|
85
|
+
|
86
|
+
# Returns a hash based representation of any data object given ejs template block
|
87
|
+
# object_to_hash(@user) { attribute :full_name } => { ... }
|
88
|
+
def object_to_hash(object, source=nil, &block)
|
89
|
+
return object unless object.is_a?(ActiveRecord::Base) || object.first.is_a?(ActiveRecord::Base)
|
90
|
+
self.class.new(@_vars.merge(:object => object), @_handler, source, &block).to_hash(:root => false)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'action_view/base'
|
2
|
+
require 'action_view/template'
|
3
|
+
|
4
|
+
module ActionView
|
5
|
+
module TemplateHandlers
|
6
|
+
class JRBHandler < TemplateHandler
|
7
|
+
include Compilable
|
8
|
+
|
9
|
+
def compile(template) %{
|
10
|
+
::JRB::Generator.new(assigns.merge(local_assigns), self) do
|
11
|
+
#{template.source}
|
12
|
+
end.to_#{template.format}
|
13
|
+
} end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
ActionView::Template.register_template_handler :jrb, ActionView::TemplateHandlers::JRBHandler
|
data/lib/rabl/version.rb
ADDED
data/rabl.gemspec
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "rabl/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "rabl"
|
7
|
+
s.version = Rabl::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ["Nathan Esquenazi"]
|
10
|
+
s.email = ["nesquena@gmail.com"]
|
11
|
+
s.homepage = "https://github.com/nesquena/rabl"
|
12
|
+
s.summary = %q{General ruby templating for json or xml}
|
13
|
+
s.description = %q{General ruby templating for json or xml}
|
14
|
+
|
15
|
+
s.rubyforge_project = "rabl"
|
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
|
+
end
|
metadata
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rabl
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 29
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 1
|
10
|
+
version: 0.0.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Nathan Esquenazi
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-04-12 00:00:00 Z
|
19
|
+
dependencies: []
|
20
|
+
|
21
|
+
description: General ruby templating for json or xml
|
22
|
+
email:
|
23
|
+
- nesquena@gmail.com
|
24
|
+
executables: []
|
25
|
+
|
26
|
+
extensions: []
|
27
|
+
|
28
|
+
extra_rdoc_files: []
|
29
|
+
|
30
|
+
files:
|
31
|
+
- .gitignore
|
32
|
+
- Gemfile
|
33
|
+
- README.md
|
34
|
+
- Rakefile
|
35
|
+
- lib/rabl.rb
|
36
|
+
- lib/rabl/builder.rb
|
37
|
+
- lib/rabl/engine.rb
|
38
|
+
- lib/rabl/template.rb
|
39
|
+
- lib/rabl/version.rb
|
40
|
+
- rabl.gemspec
|
41
|
+
homepage: https://github.com/nesquena/rabl
|
42
|
+
licenses: []
|
43
|
+
|
44
|
+
post_install_message:
|
45
|
+
rdoc_options: []
|
46
|
+
|
47
|
+
require_paths:
|
48
|
+
- lib
|
49
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
hash: 3
|
55
|
+
segments:
|
56
|
+
- 0
|
57
|
+
version: "0"
|
58
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
59
|
+
none: false
|
60
|
+
requirements:
|
61
|
+
- - ">="
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
hash: 3
|
64
|
+
segments:
|
65
|
+
- 0
|
66
|
+
version: "0"
|
67
|
+
requirements: []
|
68
|
+
|
69
|
+
rubyforge_project: rabl
|
70
|
+
rubygems_version: 1.7.2
|
71
|
+
signing_key:
|
72
|
+
specification_version: 3
|
73
|
+
summary: General ruby templating for json or xml
|
74
|
+
test_files: []
|
75
|
+
|