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
@@ -1,11 +1,11 @@
1
1
  language: ruby
2
2
  env:
3
- - "RAILS_VERSION=3.1.0"
4
3
  - "RAILS_VERSION=3.2.0"
5
4
  - "RAILS_VERSION=4.0.0"
5
+ - "RAILS_VERSION=4.1.0"
6
6
  rvm:
7
7
  - 1.9.3
8
8
  - jruby-19mode
9
- - rbx
9
+ - rbx-2
10
10
  - 2.0.0
11
11
  - 2.1.0
@@ -1,5 +1,15 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## 0.4.0
4
+ * Internal cleanup and refactor
5
+ * Remove the `allow_empty_format_in_template` option, since it has become
6
+ the default behavior.
7
+ * Remove multi_json dependency
8
+ * New options available
9
+ * replace_nil_values_with_empty_strings
10
+ * replace_empty_string_values_with_nil
11
+ * exclude_nil_values
12
+
3
13
  ## 0.3.4
4
14
  * Add `xml_options` option to root_level (brettallred)
5
15
 
data/Gemfile CHANGED
@@ -6,18 +6,20 @@ rails_version = ENV['RAILS_VERSION'] || 'default'
6
6
 
7
7
  rails = case rails_version
8
8
  when 'master'
9
- {github: 'rails/rails'}
9
+ { github: 'rails/rails' }
10
10
  when "default"
11
11
  '~> 3.2.0'
12
12
  else
13
13
  "~> #{rails_version}"
14
14
  end
15
15
 
16
+ minitest_version = rails_version == '4.0.0' ? '~> 4.7' : '~> 5.4'
17
+
16
18
  gem 'activesupport', rails
17
19
  gem 'railties', rails
18
20
 
19
- group :development, :test do
20
- gem 'actionpack', rails
21
+ group :test do
22
+ gem 'minitest', minitest_version
21
23
  end
22
24
 
23
25
  gem 'plist'
@@ -34,9 +36,3 @@ platforms :jruby do
34
36
  gem 'nokogiri'
35
37
  end
36
38
 
37
- platforms :rbx do
38
- gem 'rubysl', '~> 2.0'
39
- gem 'minitest'
40
- gem 'rubysl-test-unit'
41
- end
42
-
data/README.md CHANGED
@@ -4,7 +4,7 @@ RABL (Ruby API Builder Language) is a ruby templating system for rendering resou
4
4
 
5
5
  rabl-rails is **faster** and uses **less memory** than the standard rabl gem while letting you access the same features. There are some slight changes to do on your templates to get this gem to work but it should't take you more than 5 minutes.
6
6
 
7
- rabl-rails only targets **Rails 3+ application** and is compatible with mri 1.9.3, jRuby and rubinius.
7
+ rabl-rails only targets **Rails 3.2+ application** and is compatible with mri 1.9.3+, jRuby and rubinius.
8
8
 
9
9
  ## Installation
10
10
 
@@ -91,12 +91,14 @@ RablRails.configure do |config|
91
91
  # These are the default
92
92
  # config.cache_templates = true
93
93
  # config.include_json_root = true
94
- # config.json_engine = :oj
95
- # config.xml_engine = 'LibXML'
94
+ # config.json_engine = ::Oj
96
95
  # config.xml_options = { :dasherize => true, :skip_types => false }
97
96
  # config.use_custom_responder = false
98
97
  # config.default_responder_template = 'show'
99
98
  # config.enable_jsonp_callbacks = false
99
+ # config.replace_nil_values_with_empty_strings = false
100
+ # config.replace_empty_string_values_with_nil = false
101
+ # config.exclude_nil_values = false
100
102
  end
101
103
  ```
102
104
 
data/Rakefile CHANGED
@@ -27,8 +27,8 @@ require 'rake/testtask'
27
27
  Rake::TestTask.new(:test) do |t|
28
28
  t.libs << 'lib'
29
29
  t.libs << 'test'
30
- t.pattern = 'test/**/*_test.rb'
31
- t.verbose = true
30
+ t.pattern = 'test/**/test_*.rb'
31
+ # t.verbose = true
32
32
  end
33
33
 
34
34
 
@@ -1,96 +1,43 @@
1
1
  require 'rails/railtie'
2
2
 
3
3
  require 'active_support'
4
- require 'active_support/core_ext/class/attribute_accessors'
5
4
 
6
5
  require 'rabl-rails/version'
6
+ require 'rabl-rails/helpers'
7
7
  require 'rabl-rails/template'
8
- require 'rabl-rails/condition'
8
+ require 'rabl-rails/nodes'
9
9
  require 'rabl-rails/compiler'
10
10
 
11
+ require 'rabl-rails/visitors'
11
12
  require 'rabl-rails/renderer'
12
-
13
13
  require 'rabl-rails/library'
14
+
14
15
  require 'rabl-rails/handler'
15
16
  require 'rabl-rails/railtie'
16
17
 
17
- require 'multi_json'
18
+ require 'rabl-rails/configuration'
19
+
20
+ begin
21
+ require 'oj'
22
+ Oj.default_options = { mode: :compat, time_format: :ruby }
23
+ rescue LoadError
24
+ require 'json'
25
+ end
18
26
 
19
27
  module RablRails
20
28
  extend Renderer
21
29
 
22
- autoload :Responder, 'rabl-rails/responder'
23
-
24
- mattr_accessor :cache_templates
25
- @@cache_templates = true
26
-
27
- mattr_accessor :include_json_root
28
- @@include_json_root = true
29
-
30
- mattr_accessor :use_custom_responder
31
- @@use_custom_responder = false
32
-
33
- mattr_accessor :responder_default_template
34
- @@responder_default_template = 'show'
35
-
36
- mattr_reader :plist_engine
37
- @@plist_engine = nil
38
-
39
- mattr_accessor :include_plist_root
40
- @@include_plist_root = nil
41
-
42
- mattr_accessor :enable_jsonp_callbacks
43
- @@enable_jsonp_callbacks = false
44
-
45
- mattr_accessor :allow_empty_format_in_template
46
- @@allow_empty_format_in_template = false
47
-
48
- mattr_accessor :xml_options
49
- @@xml_options = { dasherize: true, skip_types: false }
50
-
51
- def self.configure
52
- yield self
53
-
54
- ActionController::Base.responder = Responder if self.use_custom_responder
55
- end
56
-
57
- def self.json_engine=(name)
58
- MultiJson.engine = name
59
- rescue LoadError
60
- Rails.logger.warn %Q(WARNING: rabl-rails could not load "#{name}" as JSON engine, fallback to default)
61
- end
62
-
63
- def self.json_engine
64
- MultiJson.engine
65
- end
66
-
67
- def self.xml_engine=(name)
68
- ActiveSupport::XmlMini.backend = name
69
- rescue LoadError, NameError
70
- Rails.logger.warn %Q(WARNING: rabl-rails could not load "#{name}" as XML engine, fallback to default)
71
- end
72
-
73
- def self.xml_engine
74
- ActiveSupport::XmlMini.backend
75
- end
76
-
77
- def self.plist_engine=(name)
78
- raise "Your plist engine does not respond to #dump" unless name.respond_to?(:dump)
79
- @@plist_engine = name
80
- end
81
-
82
- def self.cache_templates?
83
- ActionController::Base.perform_caching && @@cache_templates
84
- end
30
+ class << self
31
+ def configure
32
+ yield configuration
33
+ end
85
34
 
86
- def self.load_default_engines!
87
- self.json_engine = MultiJson.default_engine
88
- self.plist_engine = Plist::Emit if defined?(Plist)
35
+ def configuration
36
+ @_configuration ||= Configuration.new
37
+ end
89
38
 
90
- if defined?(LibXML)
91
- self.xml_engine = 'LibXML'
92
- elsif defined?(Nokogiri)
93
- self.xml_engine = 'Nokogiri'
39
+ def reset_configuration
40
+ @_configuration = nil
94
41
  end
95
42
  end
96
43
  end
@@ -4,8 +4,8 @@ module RablRails
4
4
  # representing data structure
5
5
  #
6
6
  class Compiler
7
- def initialize
8
- @i = -1
7
+ def initialize(view)
8
+ @view = view
9
9
  end
10
10
 
11
11
  #
@@ -41,15 +41,19 @@ module RablRails
41
41
  # attribute :email => :super_secret
42
42
  #
43
43
  def attribute(*args)
44
+ node = Nodes::Attribute.new
45
+
44
46
  if args.first.is_a?(Hash)
45
- args.first.each_pair { |k, v| @template[v] = k }
47
+ args.first.each_pair { |k, v| node[v] = k }
46
48
  else
47
49
  options = args.extract_options!
48
50
  args.each { |name|
49
51
  key = options[:as] || name
50
- @template[key] = name
52
+ node[key] = name
51
53
  }
52
54
  end
55
+
56
+ @template.add_node node
53
57
  end
54
58
  alias_method :attributes, :attribute
55
59
 
@@ -66,12 +70,14 @@ module RablRails
66
70
  data, name = extract_data_and_name(name_or_data)
67
71
  name = options[:root] if options.has_key? :root
68
72
 
69
- @template[name] = if options[:partial]
70
- template = Library.instance.compile_template_from_path(options[:partial])
71
- template.merge!(:_data => data)
73
+ if options.key?(:partial)
74
+ template = Library.instance.compile_template_from_path(options[:partial], @view)
75
+ template.data = data
72
76
  elsif block_given?
73
- sub_compile(data) { yield }
77
+ template = sub_compile(data) { yield }
74
78
  end
79
+
80
+ @template.add_node Nodes::Child.new(name, template)
75
81
  end
76
82
 
77
83
  #
@@ -81,7 +87,9 @@ module RablRails
81
87
  #
82
88
  def glue(data)
83
89
  return unless block_given?
84
- @template[sequence('glue')] = sub_compile(data) { yield }
90
+
91
+ template = sub_compile(data) { yield }
92
+ @template.add_node Nodes::Glue.new(template)
85
93
  end
86
94
 
87
95
  #
@@ -93,18 +101,8 @@ module RablRails
93
101
  # node(:role, if: ->(u) { !u.admin? }) { |u| u.role }
94
102
  #
95
103
  def node(name = nil, options = {}, &block)
96
- name ||= sequence('merge')
97
- condition = options[:if]
98
-
99
- if condition
100
- if condition.is_a?(Proc)
101
- @template[name] = [condition, block]
102
- else
103
- @template[name] = block if condition
104
- end
105
- else
106
- @template[name] = block
107
- end
104
+ return unless block_given?
105
+ @template.add_node Nodes::Code.new(name, block, options[:if])
108
106
  end
109
107
  alias_method :code, :node
110
108
 
@@ -114,9 +112,9 @@ module RablRails
114
112
  # Example:
115
113
  # merge { |item| partial("specific/#{item.to_s}", object: item) }
116
114
  #
117
- def merge(&block)
115
+ def merge
118
116
  return unless block_given?
119
- node(sequence('merge'), &block)
117
+ node(nil) { yield }
120
118
  end
121
119
 
122
120
  #
@@ -125,8 +123,7 @@ module RablRails
125
123
  # extends 'users/base'
126
124
  #
127
125
  def extends(path)
128
- t = Library.instance.compile_template_from_path(path)
129
- @template.merge!(t.source)
126
+ @template.extends Library.instance.compile_template_from_path(path, @view)
130
127
  end
131
128
 
132
129
  #
@@ -138,7 +135,7 @@ module RablRails
138
135
  #
139
136
  def condition(proc)
140
137
  return unless block_given?
141
- @template[sequence('if')] = Condition.new(proc, sub_compile(nil) { yield })
138
+ @template.add_node Nodes::Condition.new(proc, sub_compile(nil, true) { yield })
142
139
  end
143
140
 
144
141
  def cache(&block)
@@ -147,14 +144,6 @@ module RablRails
147
144
 
148
145
  protected
149
146
 
150
- #
151
- # Return unique symbol starting with given name
152
- #
153
- def sequence(name)
154
- @i += 1
155
- :"_#{name}#{@i}"
156
- end
157
-
158
147
  #
159
148
  # Extract data root_name and root name
160
149
  # Example:
@@ -173,11 +162,12 @@ module RablRails
173
162
  end
174
163
  end
175
164
 
176
- def sub_compile(data)
177
- return {} unless block_given?
178
- old_template, @template = @template, {}
165
+ def sub_compile(data, only_nodes = false)
166
+ raise unless block_given?
167
+ old_template, @template = @template, CompiledTemplate.new
179
168
  yield
180
- data ? @template.merge!(:_data => data) : @template
169
+ @template.data = data
170
+ only_nodes ? @template.nodes : @template
181
171
  ensure
182
172
  @template = old_template
183
173
  end
@@ -0,0 +1,48 @@
1
+ module RablRails
2
+ class Configuration
3
+ attr_accessor :json_engine, :include_json_root, :enable_jsonp_callbacks
4
+ attr_accessor :xml_options
5
+ attr_accessor :plist_engine, :include_plist_root
6
+ attr_accessor :cache_templates
7
+ attr_reader :use_custom_responder
8
+ attr_accessor :responder_default_template
9
+ attr_accessor :replace_nil_values_with_empty_strings
10
+ attr_accessor :replace_empty_string_values_with_nil
11
+ attr_accessor :exclude_nil_values
12
+
13
+ def initialize
14
+ @json_engine = defined?(::Oj) ? ::Oj : ::JSON
15
+ @include_json_root = true
16
+ @enable_jsonp_callbacks = false
17
+
18
+ @xml_options = { dasherize: true, skip_types: false }
19
+
20
+ @plist_engine = defined?(::Plist) ? ::Plist::Emit : nil
21
+ @include_plist_root = false
22
+
23
+ @cache_templates = ActionController::Base.perform_caching
24
+
25
+ @use_custom_responder = false
26
+ @responder_default_template = 'show'
27
+
28
+ @replace_nil_values_with_empty_strings = false
29
+ @replace_empty_string_values_with_nil = false
30
+ @exclude_nil_values = false
31
+ end
32
+
33
+ def use_custom_responder=(value)
34
+ @use_custom_responder = value
35
+ require 'rabl-rails/responder' if value
36
+ end
37
+
38
+ def result_flags
39
+ @result_flags ||= begin
40
+ result = 0
41
+ result |= 0b01 if @replace_nil_values_with_empty_strings
42
+ result |= 0b10 if @replace_empty_string_values_with_nil
43
+ result |= 0b100 if @exclude_nil_values
44
+ result
45
+ end
46
+ end
47
+ end
48
+ end
@@ -1,7 +1,9 @@
1
+ require 'active_support/core_ext/class/attribute'
2
+
1
3
  module RablRails
2
4
  module Handlers
3
5
  class Rabl
4
- cattr_accessor :default_format
6
+ class_attribute :default_format
5
7
  self.default_format = 'application/json'
6
8
 
7
9
  def self.call(template)
@@ -0,0 +1,7 @@
1
+ module RablRails
2
+ module Helpers
3
+ def collection?(resource)
4
+ resource && resource.respond_to?(:each) && !resource.is_a?(Struct)
5
+ end
6
+ end
7
+ end
@@ -1,37 +1,64 @@
1
1
  require 'singleton'
2
+ require 'monitor'
3
+ require 'thread_safe'
2
4
 
3
5
  module RablRails
4
6
  class Library
5
7
  include Singleton
6
8
 
7
9
  def initialize
8
- @cached_templates = {}
10
+ @cached_templates = ThreadSafe::Cache.new
11
+ @mutex = Monitor.new
9
12
  end
10
13
 
11
- def get_rendered_template(source, context, locals = nil)
12
- path = context.instance_variable_get(:@virtual_path)
13
- @lookup_context = context.lookup_context
14
+ def reset_cache!
15
+ @cached_templates = ThreadSafe::Cache.new
16
+ end
14
17
 
15
- compiled_template = compile_template_from_source(source, path)
18
+ def get_rendered_template(source, view, locals = nil)
19
+ compiled_template = compile_template_from_source(source, view)
20
+ format = view.params[:format] ? view.params[:format].to_s.upcase : :JSON
21
+ Renderers.const_get(format).render(compiled_template, view, locals)
22
+ end
16
23
 
17
- format = context.params[:format] ? context.params[:format].to_s.upcase : :JSON
18
- Renderers.const_get(format).new(context, locals).render(compiled_template)
24
+ def compile_template_from_source(source, view)
25
+ if RablRails.configuration.cache_templates
26
+ path = view.instance_variable_get(:@virtual_path)
27
+ synchronized_compile(path, source, view)
28
+ else
29
+ compile(source, view)
30
+ end
19
31
  end
20
32
 
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
33
+ def compile_template_from_path(path, view)
34
+ if RablRails.configuration.cache_templates
35
+ synchronized_compile(path, nil, view)
25
36
  else
26
- Compiler.new.compile_source(source)
37
+ source = fetch_source(path, view)
38
+ compile(source, view)
27
39
  end
28
40
  end
29
41
 
30
- def compile_template_from_path(path)
31
- return @cached_templates[path].dup if @cached_templates.has_key?(path)
42
+ private
43
+
44
+ def synchronized_compile(path, source, view)
45
+ @cached_templates[path] || @mutex.synchronize do
46
+ # Any thread holding this lock will be compiling the template needed
47
+ # by the threads waiting. So re-check the template presence to avoid
48
+ # re-compilation
49
+ @cached_templates.fetch(path) do
50
+ source ||= fetch_source(path, view)
51
+ @cached_templates[path] = compile(source, view)
52
+ end
53
+ end
54
+ end
55
+
56
+ def compile(source, view)
57
+ Compiler.new(view).compile_source(source)
58
+ end
32
59
 
33
- t = @lookup_context.find_template(path, [], false)
34
- compile_template_from_source(t.source, path)
60
+ def fetch_source(path, view)
61
+ view.lookup_context.find_template(path, [], false).source
35
62
  end
36
63
  end
37
64
  end