rabl-rails 0.3.4 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. data/.travis.yml +2 -2
  2. data/CHANGELOG.md +10 -0
  3. data/Gemfile +5 -9
  4. data/README.md +5 -3
  5. data/Rakefile +2 -2
  6. data/lib/rabl-rails.rb +21 -74
  7. data/lib/rabl-rails/compiler.rb +28 -38
  8. data/lib/rabl-rails/configuration.rb +48 -0
  9. data/lib/rabl-rails/handler.rb +3 -1
  10. data/lib/rabl-rails/helpers.rb +7 -0
  11. data/lib/rabl-rails/library.rb +43 -16
  12. data/lib/rabl-rails/nodes.rb +6 -0
  13. data/lib/rabl-rails/nodes/attribute.rb +17 -0
  14. data/lib/rabl-rails/nodes/child.rb +12 -0
  15. data/lib/rabl-rails/nodes/code.rb +19 -0
  16. data/lib/rabl-rails/nodes/condition.rb +14 -0
  17. data/lib/rabl-rails/nodes/glue.rb +25 -0
  18. data/lib/rabl-rails/nodes/node.rb +9 -0
  19. data/lib/rabl-rails/railtie.rb +0 -2
  20. data/lib/rabl-rails/renderer.rb +15 -13
  21. data/lib/rabl-rails/renderers/hash.rb +85 -0
  22. data/lib/rabl-rails/renderers/json.rb +9 -5
  23. data/lib/rabl-rails/renderers/plist.rb +6 -4
  24. data/lib/rabl-rails/renderers/xml.rb +6 -3
  25. data/lib/rabl-rails/responder.rb +1 -1
  26. data/lib/rabl-rails/template.rb +11 -5
  27. data/lib/rabl-rails/version.rb +1 -1
  28. data/lib/rabl-rails/visitors.rb +2 -0
  29. data/lib/rabl-rails/visitors/to_hash.rb +131 -0
  30. data/lib/rabl-rails/visitors/visitor.rb +17 -0
  31. data/rabl-rails.gemspec +3 -5
  32. data/test/helper.rb +75 -0
  33. data/test/renderers/test_hash_renderer.rb +90 -0
  34. data/test/renderers/test_json_renderer.rb +46 -0
  35. data/test/renderers/test_plist_renderer.rb +42 -0
  36. data/test/renderers/test_xml_renderer.rb +37 -0
  37. data/test/test_compiler.rb +283 -0
  38. data/test/test_configuration.rb +31 -0
  39. data/test/test_hash_visitor.rb +224 -0
  40. data/test/test_library.rb +85 -0
  41. data/test/{render_test.rb → test_render.rb} +18 -24
  42. metadata +99 -108
  43. data/lib/rabl-rails/condition.rb +0 -10
  44. data/lib/rabl-rails/renderers/base.rb +0 -171
  45. data/test/base_renderer_test.rb +0 -67
  46. data/test/cache_templates_test.rb +0 -35
  47. data/test/compiler_test.rb +0 -233
  48. data/test/deep_nesting_test.rb +0 -56
  49. data/test/keyword_test.rb +0 -47
  50. data/test/non_restful_response_test.rb +0 -35
  51. data/test/renderers/json_renderer_test.rb +0 -189
  52. data/test/renderers/plist_renderer_test.rb +0 -135
  53. data/test/renderers/xml_renderer_test.rb +0 -137
  54. data/test/test_helper.rb +0 -68
@@ -0,0 +1,6 @@
1
+ require 'rabl-rails/nodes/node'
2
+ require 'rabl-rails/nodes/attribute'
3
+ require 'rabl-rails/nodes/glue'
4
+ require 'rabl-rails/nodes/child'
5
+ require 'rabl-rails/nodes/code'
6
+ require 'rabl-rails/nodes/condition'
@@ -0,0 +1,17 @@
1
+ require 'forwardable'
2
+
3
+ module RablRails
4
+ module Nodes
5
+ class Attribute
6
+ include Node
7
+ extend Forwardable
8
+
9
+ def_delegators :@hash, :[]=, :each
10
+ attr_reader :hash
11
+
12
+ def initialize(hash = {})
13
+ @hash = hash
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,12 @@
1
+ module RablRails
2
+ module Nodes
3
+ class Child < Glue
4
+ attr_reader :name
5
+
6
+ def initialize(name, template)
7
+ super(template)
8
+ @name = name
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,19 @@
1
+ module RablRails
2
+ module Nodes
3
+ class Code
4
+ include Node
5
+
6
+ attr_reader :name, :block, :condition
7
+
8
+ def initialize(name, block, condition = nil)
9
+ @name = name
10
+ @block = block
11
+ @condition = condition
12
+ end
13
+
14
+ def merge?
15
+ !name
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,14 @@
1
+ module RablRails
2
+ module Nodes
3
+ class Condition
4
+ include Node
5
+
6
+ attr_reader :condition, :nodes
7
+
8
+ def initialize(condition, nodes)
9
+ @condition = condition
10
+ @nodes = nodes
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,25 @@
1
+ module RablRails
2
+ module Nodes
3
+ class Glue
4
+ include Node
5
+
6
+ attr_reader :template
7
+
8
+ def initialize(template)
9
+ @template = template
10
+ end
11
+
12
+ def data
13
+ @template.data
14
+ end
15
+
16
+ def nodes
17
+ @template.nodes
18
+ end
19
+
20
+ def instance_variable_data?
21
+ @instance_variable_data ||= data.to_s.start_with?('@')
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,9 @@
1
+ module RablRails
2
+ module Nodes
3
+ module Node
4
+ def accept(visitor)
5
+ visitor.visit(self)
6
+ end
7
+ end
8
+ end
9
+ end
@@ -1,8 +1,6 @@
1
1
  module RablRails
2
2
  class Railtie < Rails::Railtie
3
3
  initializer "rabl.initialize" do |app|
4
- RablRails.load_default_engines!
5
-
6
4
  ActiveSupport.on_load(:action_view) do
7
5
  ActionView::Template.register_template_handler :rabl, RablRails::Handlers::Rabl
8
6
  end
@@ -1,4 +1,4 @@
1
- require 'rabl-rails/renderers/base'
1
+ require 'rabl-rails/renderers/hash'
2
2
  require 'rabl-rails/renderers/json'
3
3
  require 'rabl-rails/renderers/xml'
4
4
  require 'rabl-rails/renderers/plist'
@@ -6,16 +6,14 @@ require 'rabl-rails/renderers/plist'
6
6
  module RablRails
7
7
  module Renderer
8
8
  class TemplateNotFound < StandardError; end
9
-
10
- mattr_reader :view_path
11
- @@view_path = 'app/views'
9
+ class PartialError < StandardError; end
12
10
 
13
11
  class LookupContext
14
12
  T = Struct.new(:source)
15
13
 
16
14
  def initialize(view_path, format)
17
- @view_path = view_path || RablRails::Renderer.view_path
18
- @extension = format ? ".#{format.to_s.downcase}.rabl" : ".rabl"
15
+ @view_path = view_path || 'app/views'
16
+ @format = format.downcase
19
17
  end
20
18
 
21
19
  #
@@ -24,8 +22,14 @@ module RablRails
24
22
  # path is used
25
23
  #
26
24
  def find_template(name, opt, partial = false)
27
- path = File.join(@view_path, "#{name}#{@extension}")
28
- File.exists?(path) ? T.new(File.read(path)) : nil
25
+ paths = Dir["#@view_path/#{name}{.#@format,}.rabl"]
26
+ file_path = paths.find { |path| File.exists?(path) }
27
+
28
+ if file_path
29
+ T.new(File.read(file_path))
30
+ else
31
+ raise TemplateNotFound
32
+ end
29
33
  end
30
34
  end
31
35
 
@@ -33,12 +37,12 @@ module RablRails
33
37
  # Context class to emulate normal Rails view
34
38
  # context
35
39
  #
36
- class Context
40
+ class ViewContext
37
41
  attr_reader :format
38
42
 
39
43
  def initialize(path, options)
40
44
  @virtual_path = path
41
- @format = options.delete(:format) || (RablRails.allow_empty_format_in_template ? nil : 'json')
45
+ @format = options.delete(:format) || 'json'
42
46
  @_assigns = {}
43
47
  @options = options
44
48
 
@@ -80,11 +84,9 @@ module RablRails
80
84
  def render(object, template, options = {})
81
85
  object = options[:locals].delete(:object) if !object && options[:locals]
82
86
 
83
- c = Context.new(template, options)
87
+ c = ViewContext.new(template, options)
84
88
  t = c.lookup_context.find_template(template, [], false)
85
89
 
86
- raise TemplateNotFound unless t
87
-
88
90
  Library.instance.get_rendered_template(t.source, c, resource: object)
89
91
  end
90
92
  end
@@ -0,0 +1,85 @@
1
+ module RablRails
2
+ module Renderers
3
+ module Hash
4
+ include ::RablRails::Helpers
5
+ extend self
6
+
7
+ #
8
+ # Render a template.
9
+ # Uses the compiled template source to get a hash with the actual
10
+ # data and then format the result according to the `format_result`
11
+ # method defined by the renderer.
12
+ #
13
+ def render(template, context, locals = nil)
14
+ visitor = Visitors::ToHash.new(context)
15
+
16
+ collection_or_resource = if template.data
17
+ if context.respond_to?(template.data)
18
+ context.send(template.data)
19
+ else
20
+ visitor.instance_variable_get(template.data)
21
+ end
22
+ end
23
+ collection_or_resource ||= locals[:resource] if locals
24
+
25
+ render_with_cache(template.cache_key, collection_or_resource) do
26
+ output_hash = if collection?(collection_or_resource)
27
+ render_collection(collection_or_resource, template.nodes, visitor)
28
+ else
29
+ render_resource(collection_or_resource, template.nodes, visitor)
30
+ end
31
+
32
+ format_output(output_hash, root_name: template.root_name, params: context.params)
33
+ end
34
+ end
35
+
36
+ protected
37
+
38
+ #
39
+ # Format a hash into the desired output.
40
+ # Renderer subclasses must implement this method
41
+ #
42
+ def format_output(hash, options = {})
43
+ hash = { options[:root_name] => hash } if options[:root_name]
44
+ hash
45
+ end
46
+
47
+ private
48
+
49
+ #
50
+ # Render a single resource as a hash, according to the compiled
51
+ # template source passed.
52
+ #
53
+ def render_resource(resource, nodes, visitor)
54
+ visitor.reset_for resource
55
+ visitor.visit nodes
56
+ visitor.result
57
+ end
58
+
59
+ #
60
+ # Call the render_resource mtehod on each object of the collection
61
+ # and return an array of the returned values.
62
+ #
63
+ def render_collection(collection, nodes, visitor)
64
+ collection.map { |o| render_resource(o, nodes, visitor) }
65
+ end
66
+
67
+ def resolve_cache_key(key, data)
68
+ return data.cache_key unless key
69
+ key.is_a?(Proc) ? instance_exec(data, &key) : key
70
+ end
71
+
72
+ private
73
+
74
+ def render_with_cache(key, collection_or_resource)
75
+ if !key.is_a?(FalseClass) && ActionController::Base.perform_caching
76
+ Rails.cache.fetch(resolve_cache_key(key, collection_or_resource)) do
77
+ yield
78
+ end
79
+ else
80
+ yield
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
@@ -1,11 +1,15 @@
1
1
  module RablRails
2
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
- json = MultiJson.encode(hash)
3
+ module JSON
4
+ include Renderers::Hash
5
+ extend self
7
6
 
8
- RablRails.enable_jsonp_callbacks && params.has_key?(:callback) ? "#{params[:callback]}(#{json})" : json
7
+ def format_output(hash, options = {})
8
+ hash = { options[:root_name] => hash } if options[:root_name] && RablRails.configuration.include_json_root
9
+ json = RablRails.configuration.json_engine.dump(hash)
10
+ params = options.fetch(:params, {})
11
+
12
+ RablRails.configuration.enable_jsonp_callbacks && params.has_key?(:callback) ? "#{params[:callback]}(#{json})" : json
9
13
  end
10
14
 
11
15
  def resolve_cache_key(key, data)
@@ -1,10 +1,12 @@
1
1
  module RablRails
2
2
  module Renderers
3
- class PLIST < Base
3
+ module PLIST
4
+ include Renderers::Hash
5
+ extend self
4
6
 
5
- def format_output(hash)
6
- hash = { _options[:root_name] => hash } if _options[:root_name] && RablRails.include_plist_root
7
- RablRails.plist_engine.dump(hash)
7
+ def format_output(hash, options = {})
8
+ hash = { options[:root_name] => hash } if options[:root_name] && RablRails.configuration.include_plist_root
9
+ RablRails.configuration.plist_engine.dump(hash)
8
10
  end
9
11
 
10
12
  def resolve_cache_key(key, data)
@@ -2,9 +2,12 @@ require 'active_support/core_ext/hash/conversions'
2
2
 
3
3
  module RablRails
4
4
  module Renderers
5
- class XML < Base
6
- def format_output(hash)
7
- xml_options = { root: _options[:root_name] }.merge!(RablRails.xml_options)
5
+ module XML
6
+ include Renderers::Hash
7
+ extend self
8
+
9
+ def format_output(hash, options = {})
10
+ xml_options = { root: options[:root_name] }.merge!(RablRails.configuration.xml_options)
8
11
  hash.to_xml(xml_options)
9
12
  end
10
13
 
@@ -31,7 +31,7 @@ module RablRails
31
31
  template = if controller.respond_to?(:responder_default_template, true)
32
32
  controller.send(:responder_default_template)
33
33
  else
34
- RablRails.responder_default_template
34
+ RablRails.configuration.responder_default_template
35
35
  end
36
36
  options[:prefixes] = controller._prefixes
37
37
  options[:template] ||= template
@@ -1,17 +1,23 @@
1
1
  module RablRails
2
2
  class CompiledTemplate
3
- attr_accessor :source, :data, :root_name, :cache_key
4
-
5
- delegate :[], :[]=, :merge!, :to => :source
3
+ attr_accessor :nodes, :data, :root_name, :cache_key
6
4
 
7
5
  def initialize
8
- @source = {}
6
+ @nodes = []
9
7
  @cache_key = false
10
8
  end
11
9
 
12
10
  def initialize_dup(other)
13
11
  super
14
- self.source = other.source.dup
12
+ self.nodes = other.nodes.dup
13
+ end
14
+
15
+ def add_node(n)
16
+ @nodes << n
17
+ end
18
+
19
+ def extends(template)
20
+ @nodes.concat template.nodes
15
21
  end
16
22
  end
17
23
  end
@@ -1,3 +1,3 @@
1
1
  module RablRails
2
- VERSION = '0.3.4'
2
+ VERSION = '0.4.0'
3
3
  end
@@ -0,0 +1,2 @@
1
+ require 'rabl-rails/visitors/visitor'
2
+ require 'rabl-rails/visitors/to_hash'
@@ -0,0 +1,131 @@
1
+ module Visitors
2
+ class ToHash < Visitor
3
+ include RablRails::Helpers
4
+
5
+ attr_reader :_resource
6
+
7
+ def initialize(view_context, resource = nil)
8
+ @_context = view_context
9
+ @_result = {}
10
+ @_resource = resource
11
+
12
+ copy_instance_variables_from_context
13
+ end
14
+
15
+ def reset_for(resource)
16
+ @_resource = resource
17
+ @_result = {}
18
+ end
19
+
20
+ def visit_Array n
21
+ n.each { |i| visit i }
22
+ end
23
+
24
+ def visit_Attribute n
25
+ n.each { |k, v| @_result[k] = _resource.send(v) }
26
+ end
27
+
28
+ def visit_Child n
29
+ object = object_from_data(_resource, n.data, n.instance_variable_data?)
30
+
31
+ @_result[n.name] = if object
32
+ collection?(object) ? object.map { |o| sub_visit(o, n.nodes) } : sub_visit(object, n.nodes)
33
+ else
34
+ nil
35
+ end
36
+ end
37
+
38
+ def visit_Code n
39
+ if !n.condition || instance_exec(_resource, &(n.condition))
40
+ result = instance_exec _resource, &(n.block)
41
+
42
+ if n.merge?
43
+ raise RablRails::Renderer::PartialError, '`merge` block should return a hash' unless result.is_a?(Hash)
44
+ @_result.merge!(result)
45
+ else
46
+ @_result[n.name] = result
47
+ end
48
+ end
49
+ end
50
+
51
+ def visit_Condition n
52
+ @_result.merge!(sub_visit(_resource, n.nodes)) if instance_exec _resource, &(n.condition)
53
+ end
54
+
55
+ def visit_Glue n
56
+ object = object_from_data(_resource, n.data, n.instance_variable_data?)
57
+ @_result.merge! sub_visit(object, n.template.nodes)
58
+ end
59
+
60
+ def result
61
+ case RablRails.configuration.result_flags
62
+ when 0
63
+ @_result
64
+ when 1
65
+ @_result.each { |k, v| @_result[k] = '' if v == nil }
66
+ when 2, 3
67
+ @_result.each { |k, v| @_result[k] = nil if v == '' }
68
+ when 4, 5
69
+ @_result.delete_if { |_, v| v == nil }
70
+ when 6
71
+ @_result.delete_if { |_, v| v == nil || v == '' }
72
+ end
73
+ end
74
+
75
+ protected
76
+
77
+ #
78
+ # If a method is called inside a 'node' property or a 'if' lambda
79
+ # it will be passed to context if it exists or treated as a standard
80
+ # missing method.
81
+ #
82
+ def method_missing(name, *args, &block)
83
+ @_context.respond_to?(name) ? @_context.send(name, *args, &block) : super
84
+ end
85
+
86
+ #
87
+ # Allow to use partial inside of node blocks (they are evaluated at
88
+ # rendering time).
89
+ #
90
+ def partial(template_path, options = {})
91
+ raise RablRails::Renderer::PartialError.new("No object was given to partial #{template_path}") unless options[:object]
92
+ object = options[:object]
93
+
94
+ return [] if object.respond_to?(:empty?) && object.empty?
95
+
96
+ template = RablRails::Library.instance.compile_template_from_path(template_path, @_context)
97
+ if object.respond_to?(:each)
98
+ object.map { |o| sub_visit o, template.nodes }
99
+ else
100
+ sub_visit object, template.nodes
101
+ end
102
+ end
103
+
104
+ private
105
+
106
+ def copy_instance_variables_from_context
107
+ @_context.instance_variable_get(:@_assigns).each_pair { |k, v|
108
+ instance_variable_set("@#{k}", v) unless k.to_s.start_with?('_')
109
+ }
110
+ end
111
+
112
+ def sub_visit(resource, nodes)
113
+ old_result, old_resource, @_result = @_result, @_resource, {}
114
+ reset_for resource
115
+ visit nodes
116
+ result
117
+ ensure
118
+ @_result, @_resource = old_result, old_resource
119
+ end
120
+
121
+ def object_from_data(resource, symbol, is_variable)
122
+ return resource if symbol == nil
123
+
124
+ if is_variable
125
+ instance_variable_get(symbol)
126
+ else
127
+ resource.respond_to?(symbol) ? resource.send(symbol) : @_context.send(symbol)
128
+ end
129
+ end
130
+ end
131
+ end