rabl-rails 0.3.4 → 0.4.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.
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