rabl-rails 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,15 @@
1
+ module RablRails
2
+ module Handlers
3
+ class Rabl
4
+ cattr_accessor :default_format
5
+ self.default_format = 'application/json'
6
+
7
+ def self.call(template)
8
+ %{
9
+ RablRails::Library.instance.
10
+ get_rendered_template(#{template.source.inspect}, self)
11
+ }
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,37 @@
1
+ require 'singleton'
2
+
3
+ module RablRails
4
+ class Library
5
+ include Singleton
6
+
7
+ def initialize
8
+ @cached_templates = {}
9
+ end
10
+
11
+ def get_rendered_template(source, context)
12
+ path = context.instance_variable_get(:@virtual_path)
13
+ @lookup_context = context.lookup_context
14
+
15
+ compiled_template = compile_template_from_source(source, path)
16
+
17
+ format = context.params[:format] || 'json'
18
+ Renderers.const_get(format.upcase!).new(context).render(compiled_template)
19
+ end
20
+
21
+ def compile_template_from_source(source, path = nil)
22
+ if path && RablRails.cache_templates?
23
+ @cached_templates[path] ||= Compiler.new.compile_source(source)
24
+ @cached_templates[path].dup
25
+ else
26
+ Compiler.new.compile_source(source)
27
+ end
28
+ end
29
+
30
+ def compile_template_from_path(path)
31
+ template = @cached_templates[path]
32
+ return template if template
33
+ t = @lookup_context.find_template(path, [], false)
34
+ compile_template_from_source(t.source, path)
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,9 @@
1
+ module RablRails
2
+ class Railtie < Rails::Railtie
3
+ initializer "rabl.initialize" do |app|
4
+ ActiveSupport.on_load(:action_view) do
5
+ ActionView::Template.register_template_handler :rabl, RablRails::Handlers::Rabl
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,2 @@
1
+ require 'rabl-rails/renderers/base'
2
+ require 'rabl-rails/renderers/json'
@@ -0,0 +1,116 @@
1
+ module RablRails
2
+ module Renderers
3
+ class PartialError < StandardError; end
4
+
5
+ class Base
6
+ attr_accessor :options
7
+
8
+ def initialize(context) # :nodoc:
9
+ @_context = context
10
+ @options = {}
11
+ setup_render_context
12
+ end
13
+
14
+ #
15
+ # Render a template.
16
+ # Uses the compiled template source to get a hash with the actual
17
+ # data and then format the result according to the `format_result`
18
+ # method defined by the renderer.
19
+ #
20
+ def render(template)
21
+ collection_or_resource = @_context.instance_variable_get(template.data) if template.data
22
+ output_hash = collection_or_resource.respond_to?(:each) ? render_collection(collection_or_resource, template.source) :
23
+ render_resource(collection_or_resource, template.source)
24
+ options[:root_name] = template.root_name
25
+ format_output(output_hash)
26
+ end
27
+
28
+ #
29
+ # Format a hash into the desired output.
30
+ # Renderer subclasses must implement this method
31
+ #
32
+ def format_output(hash)
33
+ raise "Muse be implemented by renderer"
34
+ end
35
+
36
+ protected
37
+
38
+ #
39
+ # Render a single resource as a hash, according to the compiled
40
+ # template source passed.
41
+ #
42
+ def render_resource(data, source)
43
+ source.inject({}) { |output, current|
44
+ key, value = current
45
+
46
+ out = case value
47
+ when Symbol
48
+ data.send(value) # attributes
49
+ when Proc
50
+ instance_exec data, &value # node
51
+ when Array # node with condition
52
+ next output if !instance_exec data, &(value.first)
53
+ instance_exec data, &(value.last)
54
+ when Hash
55
+ current_value = value.dup
56
+ data_symbol = current_value.delete(:_data)
57
+ object = data_symbol.nil? ? data : data_symbol.to_s.start_with?('@') ? @_context.instance_variable_get(data_symbol) : data.send(data_symbol)
58
+
59
+ if key.to_s.start_with?('_') # glue
60
+ current_value.each_pair { |k, v|
61
+ output[k] = object.send(v)
62
+ }
63
+ next output
64
+ else # child
65
+ object.respond_to?(:each) ? render_collection(object, current_value) : render_resource(object, current_value)
66
+ end
67
+ end
68
+ output[key] = out
69
+ output
70
+ }
71
+ end
72
+
73
+ #
74
+ # Call the render_resource mtehod on each object of the collection
75
+ # and return an array of the returned values.
76
+ #
77
+ def render_collection(collection, source)
78
+ collection.map { |o| render_resource(o, source) }
79
+ end
80
+
81
+ #
82
+ # Allow to use partial inside of node blocks (they are evaluated at)
83
+ # rendering time.
84
+ #
85
+ def partial(template_path, options = {})
86
+ raise PartialError.new("No object was given to partial #{template_path}") unless options[:object]
87
+ object = options[:object]
88
+
89
+ return [] if object.respond_to?(:empty?) && object.empty?
90
+
91
+ template = Library.instance.compile_template_from_path(template_path)
92
+ object.respond_to?(:each) ? render_collection(object, template.source) : render_resource(object, template.source)
93
+ end
94
+
95
+ #
96
+ # If a method is called inside a 'node' property or a 'if' lambda
97
+ # it will be passed to context if it exists or treated as a standard
98
+ # missing method.
99
+ #
100
+ def method_missing(name, *args, &block)
101
+ @_context.respond_to?(name) ? @_context.send(name, *args, &block) : super
102
+ end
103
+
104
+ #
105
+ # Copy assigns from controller's context into this
106
+ # renderer context to include instances variables when
107
+ # evaluating 'node' properties.
108
+ #
109
+ def setup_render_context
110
+ @_context.instance_variable_get(:@_assigns).each_pair { |k, v|
111
+ instance_variable_set("@#{k}", v) unless k.start_with?('_') || k == @data
112
+ }
113
+ end
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,10 @@
1
+ module RablRails
2
+ module Renderers
3
+ class JSON < Base
4
+ def format_output(hash)
5
+ hash = { options[:root_name] => hash } if options[:root_name] && RablRails.include_json_root
6
+ MultiJson.encode(hash)
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,11 @@
1
+ module RablRails
2
+ class CompiledTemplate
3
+ attr_accessor :source, :data, :root_name
4
+
5
+ delegate :[], :[]=, :merge!, :to => :source
6
+
7
+ def initialize
8
+ @source = {}
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,3 @@
1
+ module RablRails
2
+ VERSION = '0.1.0'
3
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :rabl-rails do
3
+ # # Task goes here
4
+ # end
@@ -0,0 +1,23 @@
1
+ $:.push File.expand_path("../lib", __FILE__)
2
+ require "rabl-rails/version"
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = "rabl-rails"
6
+ s.version = RablRails::VERSION
7
+ s.platform = Gem::Platform::RUBY
8
+ s.authors = ["Christopher Cocchi-Perrier"]
9
+ s.email = ["cocchi.c@gmail.com"]
10
+ s.homepage = "https://github.com/ccocchi/rabl-rails"
11
+ s.summary = "Fast Rails 3+ templating system with JSON and XML support"
12
+ s.description = "Fast Rails 3+ templating system with JSON and XML support"
13
+
14
+ s.files = `git ls-files`.split("\n")
15
+ s.test_files = `git ls-files -- test/*`.split("\n")
16
+ s.require_paths = ["lib"]
17
+
18
+ s.add_dependency "activesupport", "~> 3.0"
19
+ s.add_dependency "railties", "~> 3.0"
20
+
21
+ s.add_development_dependency "sqlite3"
22
+ s.add_development_dependency "actionpack", "~> 3.0"
23
+ end
@@ -0,0 +1,34 @@
1
+ require 'test_helper'
2
+
3
+ class CacheTemplatesTest < ActiveSupport::TestCase
4
+
5
+ setup do
6
+ RablRails::Library.reset_instance
7
+ @library = RablRails::Library.instance
8
+ RablRails.cache_templates = true
9
+ end
10
+
11
+ test "cache templates if perform_caching is active and cache_templates is enabled" do
12
+ ActionController::Base.stub(:perform_caching).and_return(true)
13
+ @library.compile_template_from_source('', 'some/path')
14
+ t = @library.compile_template_from_source("attribute :id", 'some/path')
15
+
16
+ assert_equal({}, t.source)
17
+ end
18
+
19
+ test "cached templates should not be modifiable in place" do
20
+ ActionController::Base.stub(:perform_caching).and_return(true)
21
+ @library.compile_template_from_source('', 'some/path')
22
+ t = @library.compile_template_from_source("attribute :id", 'some/path')
23
+
24
+ assert_equal({}, t.source)
25
+ end
26
+
27
+ test "don't cache templates cache_templates is enabled but perform_caching is not active" do
28
+ ActionController::Base.stub(:perform_caching).and_return(false)
29
+ @library.compile_template_from_source('', 'some/path')
30
+ t = @library.compile_template_from_source("attribute :id", 'some/path')
31
+
32
+ assert_equal({ :id => :id }, t.source)
33
+ end
34
+ end
@@ -0,0 +1,163 @@
1
+ require 'test_helper'
2
+
3
+ class CompilerTest < ActiveSupport::TestCase
4
+
5
+ setup do
6
+ @user = User.new
7
+ @compiler = RablRails::Compiler.new
8
+ end
9
+
10
+ test "compiler return a compiled template" do
11
+ assert_instance_of RablRails::CompiledTemplate, @compiler.compile_source("")
12
+ end
13
+
14
+ test "object set data for the template" do
15
+ t = @compiler.compile_source(%{ object :@user })
16
+ assert_equal :@user, t.data
17
+ assert_equal({}, t.source)
18
+ end
19
+
20
+ test "object property can define root name" do
21
+ t = @compiler.compile_source(%{ object :@user => :author })
22
+ assert_equal :@user, t.data
23
+ assert_equal :author, t.root_name
24
+ assert_equal({}, t.source)
25
+ end
26
+
27
+ test "collection set the data for the template" do
28
+ t = @compiler.compile_source(%{ collection :@user })
29
+ assert_equal :@user, t.data
30
+ assert_equal({}, t.source)
31
+ end
32
+
33
+ test "collection property can define root name" do
34
+ t = @compiler.compile_source(%{ collection :@user => :users })
35
+ assert_equal :@user, t.data
36
+ assert_equal :users, t.root_name
37
+ assert_equal({}, t.source)
38
+ end
39
+
40
+ test "collection property can define root name via options" do
41
+ t = @compiler.compile_source(%{ collection :@user, :root => :users })
42
+ assert_equal :@user, t.data
43
+ assert_equal :users, t.root_name
44
+ end
45
+
46
+ test "root can be set to false via options" do
47
+ t = @compiler.compile_source(%( object :@user, root: false))
48
+ assert_equal false, t.root_name
49
+ end
50
+
51
+ # Compilation
52
+
53
+ test "simple attributes are compiled to hash" do
54
+ t = @compiler.compile_source(%{ attributes :id, :name })
55
+ assert_equal({ :id => :id, :name => :name}, t.source)
56
+ end
57
+
58
+ test "attributes appeared only once even if called mutiple times" do
59
+ t = @compiler.compile_source(%{ attribute :id ; attribute :id })
60
+ assert_equal({ :id => :id }, t.source)
61
+ end
62
+
63
+ test "attribute can be aliased through :as option" do
64
+ t = @compiler.compile_source(%{ attribute :foo, :as => :bar })
65
+ assert_equal({ :bar => :foo}, t.source)
66
+ end
67
+
68
+ test "attribute can be aliased through hash" do
69
+ t = @compiler.compile_source(%{ attribute :foo => :bar })
70
+ assert_equal({ :bar => :foo }, t.source)
71
+ end
72
+
73
+ test "multiple attributes can be aliased" do
74
+ t = @compiler.compile_source(%{ attributes :foo => :bar, :id => :uid })
75
+ assert_equal({ :bar => :foo, :uid => :id }, t.source)
76
+ end
77
+
78
+ test "child with association use association name as data" do
79
+ t = @compiler.compile_source(%{ child :address do attributes :foo end})
80
+ assert_equal({ :address => { :_data => :address, :foo => :foo } }, t.source)
81
+ end
82
+
83
+ test "child with association can be aliased" do
84
+ t = @compiler.compile_source(%{ child :address => :bar do attributes :foo end})
85
+ assert_equal({ :bar => { :_data => :address, :foo => :foo } }, t.source)
86
+ end
87
+
88
+ test "child with root name defined as option" do
89
+ t = @compiler.compile_source(%{ child(:user, :root => :author) do attributes :foo end })
90
+ assert_equal({ :author => { :_data => :user, :foo => :foo } }, t.source)
91
+ end
92
+
93
+ test "child with arbitrary source store the data with the template" do
94
+ t = @compiler.compile_source(%{ child :@user => :author do attribute :name end })
95
+ assert_equal({ :author => { :_data => :@user, :name => :name } }, t.source)
96
+ end
97
+
98
+ test "child with succint partial notation" do
99
+ mock_template = RablRails::CompiledTemplate.new
100
+ mock_template.source = { :id => :id }
101
+ RablRails::Library.reset_instance
102
+ RablRails::Library.instance.stub(:compile_template_from_path).with('users/base').and_return(mock_template)
103
+
104
+ t = @compiler.compile_source(%{child(:user, :partial => 'users/base') })
105
+ assert_equal( {:user => { :_data => :user, :id => :id } }, t.source)
106
+ end
107
+
108
+ test "glue is compiled as a child but with anonymous name" do
109
+ t = @compiler.compile_source(%{ glue(:@user) do attribute :name end })
110
+ assert_equal({ :_glue0 => { :_data => :@user, :name => :name } }, t.source)
111
+ end
112
+
113
+ test "multiple glue don't come with name collisions" do
114
+ t = @compiler.compile_source(%{
115
+ glue :@user do attribute :name end
116
+ glue :@user do attribute :foo end
117
+ })
118
+
119
+ assert_equal({
120
+ :_glue0 => { :_data => :@user, :name => :name},
121
+ :_glue1 => { :_data => :@user, :foo => :foo}
122
+ }, t.source)
123
+ end
124
+
125
+ test "extends use other template source as itself" do
126
+ template = mock('template', :source => { :id => :id })
127
+ RablRails::Library.reset_instance
128
+ RablRails::Library.instance.stub(:compile_template_from_path).with('users/base').and_return(template)
129
+ t = @compiler.compile_source(%{ extends 'users/base' })
130
+ assert_equal({ :id => :id }, t.source)
131
+ end
132
+
133
+ test "node are compiled without evaluating the block" do
134
+ t = @compiler.compile_source(%{ node(:foo) { bar } })
135
+ assert_not_nil t.source[:foo]
136
+ assert_instance_of Proc, t.source[:foo]
137
+ end
138
+
139
+ test "node with condition are compiled as an array of procs" do
140
+ t = @compiler.compile_source(%{ node(:foo, :if => lambda { |m| m.foo.present? }) do |m| m.foo end })
141
+ assert_not_nil t.source[:foo]
142
+ assert_instance_of Array, t.source[:foo]
143
+ assert_equal 2, t.source[:foo].size
144
+ end
145
+
146
+ test "compile with no object" do
147
+ t = @compiler.compile_source(%{
148
+ object false
149
+ child(:@user => :user) do
150
+ attribute :id
151
+ end
152
+ })
153
+
154
+ assert_equal({ :user => { :_data => :@user, :id => :id } }, t.source)
155
+ assert_equal false, t.data
156
+ end
157
+
158
+ test "name extraction from argument" do
159
+ assert_equal [:@users, 'users'], @compiler.send(:extract_data_and_name, :@users)
160
+ assert_equal [:users, :users], @compiler.send(:extract_data_and_name, :users)
161
+ assert_equal [:@users, :authors], @compiler.send(:extract_data_and_name, :@users => :authors)
162
+ end
163
+ end