rabl-rails 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.
@@ -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