rabl 0.11.1 → 0.11.2

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.
data/lib/rabl/helpers.rb CHANGED
@@ -10,8 +10,9 @@ module Rabl
10
10
  # data_object(@user => :person) => @user
11
11
  # data_object(:user => :person) => @_object.send(:user)
12
12
  def data_object(data)
13
- data = (data.is_a?(Hash) && data.keys.size == 1) ? data.keys.first : data
14
- data.is_a?(Symbol) && defined?(@_object) && @_object && @_object.respond_to?(data) ? @_object.__send__(data) : data
13
+ data = data.keys.first if data.is_a?(Hash) && data.keys.size == 1
14
+ data = @_object.__send__(data) if data.is_a?(Symbol) && defined?(@_object) && @_object && @_object.respond_to?(data)
15
+ data
15
16
  end
16
17
 
17
18
  # data_object_attribute(data) => @_object.send(data)
@@ -28,14 +29,19 @@ module Rabl
28
29
  # data_name([]) => "array"
29
30
  def data_name(data_token)
30
31
  return unless data_token # nil or false
32
+
31
33
  return data_token.values.first if data_token.is_a?(Hash) # @user => :user
34
+
32
35
  data = data_object(data_token)
36
+
33
37
  if is_collection?(data) # data is a collection
34
38
  object_name = data.table_name if data.respond_to?(:table_name)
39
+
35
40
  if object_name.nil? && data.respond_to?(:first)
36
41
  first = data.first
37
42
  object_name = data_name(first).to_s.pluralize if first.present?
38
43
  end
44
+
39
45
  object_name ||= data_token if data_token.is_a?(Symbol)
40
46
  object_name
41
47
  elsif is_object?(data) # data is an object
@@ -54,8 +60,9 @@ module Rabl
54
60
  # determine_object_root(@user, :user, true) => "user"
55
61
  # determine_object_root(@user, :person) => "person"
56
62
  # determine_object_root([@user, @user]) => "user"
57
- def determine_object_root(data_token, data_name=nil, include_root=true)
63
+ def determine_object_root(data_token, data_name = nil, include_root = true)
58
64
  return if object_root_name == false
65
+
59
66
  root_name = data_name.to_s if include_root
60
67
  if is_object?(data_token) || data_token.nil?
61
68
  root_name
@@ -78,13 +85,17 @@ module Rabl
78
85
  def is_collection?(obj, follow_symbols = true)
79
86
  data_obj = follow_symbols ? data_object(obj) : obj
80
87
  data_obj && data_obj.respond_to?(:map) && data_obj.respond_to?(:each) &&
81
- obj.class.ancestors.none? { |a| KNOWN_OBJECT_CLASSES.include? a.name }
88
+ obj.class.ancestors.none? { |a| KNOWN_OBJECT_CLASSES.include?(a.name) }
82
89
  end
83
90
 
84
- # Returns the scope wrapping this engine, used for retrieving data, invoking methods, etc
91
+ # Returns the context_scope wrapping this engine, used for retrieving data, invoking methods, etc
85
92
  # In Rails, this is the controller and in Padrino this is the request context
86
93
  def context_scope
87
- defined?(@_scope) ? @_scope : nil
94
+ defined?(@_context_scope) ? @_context_scope : nil
95
+ end
96
+
97
+ def view_path
98
+ defined?(@_view_path) ? @_view_path : nil
88
99
  end
89
100
 
90
101
  # Returns the root (if any) name for an object within a collection
@@ -106,14 +117,34 @@ module Rabl
106
117
  val.is_a?(String) || val.is_a?(Symbol)
107
118
  end
108
119
 
120
+ # Returns an Engine based representation of any data object given ejs template block
121
+ # object_to_hash(@user) { attribute :full_name } => { ... }
122
+ # object_to_hash(@user, :source => "...") { attribute :full_name } => { ... }
123
+ # object_to_hash([@user], :source => "...") { attribute :full_name } => { ... }
124
+ # options must have :source (rabl file contents)
125
+ # options can have :source_location (source filename)
126
+ def object_to_engine(object, options = {}, &block)
127
+ return if object.nil?
128
+
129
+ return [] if is_collection?(object) && object.blank? # empty collection
130
+
131
+ options = {
132
+ :format => "hash",
133
+ :view_path => view_path,
134
+ :root => (options[:root] || false)
135
+ }.merge(options)
136
+
137
+ Engine.new(options[:source], options).apply(context_scope, :object => object, :locals => options[:locals], &block)
138
+ end
139
+
109
140
  # Fetches a key from the cache and stores rabl template result otherwise
110
141
  # fetch_from_cache('some_key') { ...rabl template result... }
111
- def fetch_result_from_cache(cache_key, cache_options=nil, &block)
142
+ def fetch_result_from_cache(cache_key, cache_options = nil, &block)
112
143
  expanded_cache_key = ActiveSupport::Cache.expand_cache_key(cache_key, :rabl)
113
144
  Rabl.configuration.cache_engine.fetch(expanded_cache_key, cache_options, &block)
114
145
  end
115
146
 
116
- def write_result_to_cache(cache_key, cache_options=nil, &block)
147
+ def write_result_to_cache(cache_key, cache_options = nil, &block)
117
148
  expanded_cache_key = ActiveSupport::Cache.expand_cache_key(cache_key, :rabl)
118
149
  result = yield
119
150
  Rabl.configuration.cache_engine.write(expanded_cache_key, result, cache_options)
@@ -1,23 +1,32 @@
1
1
  module Rabl
2
2
  class MultiBuilder
3
+ include Helpers
4
+
3
5
  # Constructs a new MultiBuilder given the data and options.
4
6
  # The options will be re-used for all Rabl::Builders.
5
7
  # Rabl::MultiBuilder.new([#<User ...>, #<User ...>, ...], { :format => 'json', :child_root => true })
6
- def initialize(data, options={})
7
- @data = data
8
- @options = options
9
- @builders = []
10
- @engine_to_builder = {}
11
- @cache_key_to_engine = {}
8
+ def initialize(data, settings = {}, options = {})
9
+ @data = data
10
+ @settings = settings
11
+ @options = options
12
+ @builders = []
13
+ @engine_to_builder = {}
14
+ @cache_key_to_engine = {}
12
15
  end
13
16
 
14
17
  # Returns the result of all of the builders as an array
15
18
  def to_a
16
19
  generate_builders
17
- read_cache_results
18
- replace_engines_with_cache_results
19
20
 
20
- @builders.map { |builder| builder.to_hash(@options) }
21
+ if template_cache_configured? && Rabl.configuration.use_read_multi
22
+ map_engines_to_builders
23
+ read_cache_results
24
+ replace_engines_with_cache_results
25
+ end
26
+
27
+ result = @builders.map(&:to_hash)
28
+ result = result.map(&:presence).compact if Rabl.configuration.exclude_empty_values_in_collections
29
+ result
21
30
  end
22
31
 
23
32
  private
@@ -26,12 +35,13 @@ module Rabl
26
35
  # and maps the cache keys for each of the engines
27
36
  # the builders generated
28
37
  def generate_builders
29
- @data.each do |object|
30
- builder = Rabl::Builder.new(@options)
31
- builder.build(object, @options.merge(:keep_engines => true))
32
-
33
- @builders << builder
38
+ @builders = @data.map do |object|
39
+ Builder.new(object, @settings, @options)
40
+ end
41
+ end
34
42
 
43
+ def map_engines_to_builders
44
+ @builders.each do |builder|
35
45
  builder.engines.each do |engine|
36
46
  @engine_to_builder[engine] = builder
37
47
 
data/lib/rabl/partials.rb CHANGED
@@ -1,113 +1,18 @@
1
1
  module Rabl
2
2
  module Partials
3
- include Rabl::Helpers
3
+ include Helpers
4
+ include Sources
4
5
 
5
- # Returns a hash representing the partial
6
- # partial("users/show", :object => @user)
7
- # options must have :object
8
- # options can have :view_path, :child_root, :root
9
- def partial(file, options={}, &block)
10
- engine = self.partial_as_engine(file, options, &block)
11
- engine.is_a?(Rabl::Engine) ? engine.render : engine
12
- end
13
-
14
- def partial_as_engine(file, options={}, &block)
6
+ def partial_as_engine(file, options = {}, &block)
15
7
  raise ArgumentError, "Must provide an :object option to render a partial" unless options.has_key?(:object)
16
- object, view_path = options.delete(:object), options[:view_path] || @_view_path
17
- source, location = self.fetch_source(file, :view_path => view_path)
18
- engine_options = options.merge(:source => source, :source_location => location, :template => file)
19
- self.object_to_engine(object, engine_options, &block)
20
- end
21
-
22
- # Returns an Engine based representation of any data object given ejs template block
23
- # object_to_hash(@user) { attribute :full_name } => { ... }
24
- # object_to_hash(@user, :source => "...") { attribute :full_name } => { ... }
25
- # object_to_hash([@user], :source => "...") { attribute :full_name } => { ... }
26
- # options must have :source (rabl file contents)
27
- # options can have :source_location (source filename)
28
- def object_to_engine(object, options={}, &block)
29
- return object unless !object.nil?
30
- return [] if is_collection?(object) && object.blank? # empty collection
31
- engine_options = options.reverse_merge(:format => "hash", :view_path => @_view_path, :root => (options[:root] || false))
32
- Rabl::Engine.new(options[:source], engine_options).apply(@_scope, :object => object, :locals => options[:locals], &block)
33
- end
34
8
 
35
- # Returns source for a given relative file
36
- # fetch_source("show", :view_path => "...") => "...contents..."
37
- def fetch_source(file, options={})
38
- view_paths = Array(options[:view_path]) + Array(Rabl.configuration.view_paths)
39
- Rabl.source_cache(file, view_paths) do
40
- file_path = if defined?(Padrino) && context_scope.respond_to?(:settings) && context_scope.respond_to?(:resolve_template)
41
- fetch_padrino_source(file, options)
42
- elsif defined?(Rails) && context_scope.respond_to?(:view_paths)
43
- _view_paths = view_paths + Array(context_scope.view_paths.to_a)
44
- fetch_rails_source(file, options) || fetch_manual_template(_view_paths, file)
45
- elsif defined?(Sinatra) && context_scope.respond_to?(:settings)
46
- fetch_sinatra_source(file, options)
47
- else # generic template resolution
48
- fetch_manual_template(view_paths, file)
49
- end
9
+ object = options.delete(:object)
10
+ view_path = options[:view_path] || view_path
50
11
 
51
- unless File.exist?(file_path.to_s)
52
- raise "Cannot find rabl template '#{file}' within registered (#{view_paths.map(&:to_s).inspect}) view paths!"
53
- end
12
+ source, location = fetch_source(file, :view_path => view_path)
54
13
 
55
- [File.read(file_path.to_s), file_path.to_s]
56
- end
14
+ options = options.merge(:source => source, :source_location => location, :template => file)
15
+ object_to_engine(object, options, &block)
57
16
  end
58
-
59
- private
60
-
61
- # Returns the rabl template path for padrino views using configured views
62
- def fetch_padrino_source(file, options={})
63
- view_path = Array(options[:view_path] || context_scope.settings.views)
64
- # use Padrino's own template resolution mechanism
65
- file_path, _ = context_scope.instance_eval { resolve_template(file) }
66
- # Padrino chops the extension, stitch it back on
67
- File.join(view_path.first.to_s, (file_path.to_s + ".rabl"))
68
- end
69
-
70
- # Returns the rabl template path for Rails, including special lookups for Rails 2 and 3
71
- def fetch_rails_source(file, options={})
72
- # use Rails template resolution mechanism if possible (find_template)
73
- source_format = request_format if defined?(request_format)
74
- if source_format && context_scope.respond_to?(:lookup_context) # Rails 3
75
- lookup_proc = lambda { |partial|
76
- if ActionPack::VERSION::MAJOR == 3 && ActionPack::VERSION::MINOR < 2
77
- context_scope.lookup_context.find(file, [], partial)
78
- else # Rails 3.2 and higher
79
- # pull format directly from rails unless it is html
80
- request_format = context_scope.request.format.to_sym
81
- source_format = request_format unless request_format == :html
82
- context_scope.lookup_context.find(file, [], partial, [], {:formats => [source_format]})
83
- end }
84
- template = lookup_proc.call(false) rescue nil
85
- template ||= lookup_proc.call(true) rescue nil
86
- template.identifier if template
87
- elsif source_format && context_scope.respond_to?(:view_paths) # Rails 2
88
- template = context_scope.view_paths.find_template(file, source_format, false)
89
- template.filename if template
90
- end
91
- end
92
-
93
- # Returns the rabl template path for sinatra views using configured views
94
- def fetch_sinatra_source(file, options={})
95
- view_path = Array(options[:view_path] || context_scope.settings.views)
96
- fetch_manual_template(view_path, file)
97
- end
98
-
99
- # Returns the rabl template by looking up files within the view_path and specified file path
100
- def fetch_manual_template(view_path, file)
101
- Dir[File.join("{#{view_path.join(",")}}", "{#{file},#{partialized(file)}}" + ".{*.,}rabl")].first
102
- end
103
-
104
- # Returns a partialized version of a file path
105
- # partialized("v1/variants/variant") => "v1/variants/_variant"
106
- def partialized(file)
107
- partial_file = file.split(File::SEPARATOR)
108
- partial_file[-1] = "_#{partial_file[-1]}" unless partial_file[-1].start_with?("_")
109
- partial_file.join(File::SEPARATOR)
110
- end
111
-
112
- end # Partials
113
- end # Rabl
17
+ end
18
+ end
data/lib/rabl/railtie.rb CHANGED
@@ -1,6 +1,5 @@
1
1
  module Rabl
2
2
  class Railtie < Rails::Railtie
3
-
4
3
  initializer "rabl.initialize" do |app|
5
4
  ActiveSupport.on_load(:action_view) do
6
5
  Rabl.register!
@@ -12,6 +11,5 @@ module Rabl
12
11
  end
13
12
  end
14
13
  end
15
-
16
14
  end # Railtie
17
15
  end # Rabl
data/lib/rabl/renderer.rb CHANGED
@@ -11,6 +11,8 @@ module Rabl
11
11
  CODE
12
12
  end
13
13
 
14
+ attr_reader :object, :options
15
+
14
16
  # Public: Instantiate a new renderer
15
17
  # This is a standalone class used for rendering rabl templates
16
18
  # outside of a framework like Rails. You may want to use
@@ -21,69 +23,77 @@ module Rabl
21
23
  # renderer = Rabl::Renderer.new('template_name', user, { :format => 'json', :view_path => 'app/views' })
22
24
  # renderer.render # => '{"user":{"name": "ivan" }}'
23
25
  #
24
- attr_reader :object, :options
25
26
  def initialize(source, object = nil, options = {})
26
27
  options = {
27
- :format => :json,
28
- :scope => self,
29
- :view_path => [],
30
- :template => source
31
- }.update(options)
28
+ :format => :json,
29
+ :scope => self,
30
+ :view_path => [],
31
+ :template => source
32
+ }.merge(options)
32
33
 
33
- @options = options
34
- @object = object
34
+ @options = options
35
+ @object = object
35
36
 
36
- engine.source = self.process_source(source)
37
+ engine.source = process_source(source)
37
38
  end
38
39
 
39
40
  # Public: Actually render the template to the requested output format.
40
41
  #
41
42
  # - context_scope:
42
- # Override the render scope to the 'scope' object. Defaults to self.
43
+ # Override the render context_scope to the 'context_scope' object. Defaults to self.
43
44
  #
44
45
  # Returns: And object representing the tranformed object in the requested format.
45
46
  # e.g. json, xml, bson, plist
46
47
  def render(context_scope = nil)
47
- context_scope = context_scope ? context_scope : options.delete(:scope) || self
48
- set_instance_variable(object) if context_scope == self
49
- locals = options.fetch(:locals, {}).reverse_merge(:object => object)
48
+ context_scope ||= options[:scope] || self
49
+
50
+ set_object_instance_variable if context_scope == self
51
+
52
+ locals = { :object => object }.merge(options.fetch(:locals, {}))
53
+
50
54
  engine.apply(context_scope, locals).render
51
55
  end
52
56
 
53
57
  protected
58
+ def engine
59
+ @engine ||= Rabl::Engine.new(nil, options)
60
+ end
54
61
 
55
- def engine
56
- @engine ||= Rabl::Engine.new(nil, options)
57
- end
62
+ # Returns the source given a relative template path
63
+ def process_source(source)
64
+ return source if source.is_a?(String) && source =~ /\n/
58
65
 
59
- # Returns the source given a relative template path
60
- def process_source(source)
61
- return source if source.is_a?(String) && source =~ /\n/
62
- source, _ = engine.fetch_source(source, { :view_path => options[:view_path] })
63
- source
64
- end
66
+ source, _ = engine.fetch_source(source, { :view_path => options[:view_path] })
67
+ source
68
+ end
65
69
 
66
- # Internal: Sets an instance variable named after the class of `object`
67
- #
68
- # Example:
69
- # object.class.name # => User
70
- # set_instance_variable(object) # => @user
71
- #
72
- def set_instance_variable(object)
73
- name = model_name(object).split('/').last
74
- instance_variable_set(:"@#{name}", object)
75
- end
70
+ # Internal: Sets an instance variable named after the class of `object`
71
+ #
72
+ # Example:
73
+ # object.class.name # => User
74
+ # set_object_instance_variable # => @user == object
75
+ #
76
+ def set_object_instance_variable
77
+ instance_variable_set(:"@#{object_model_name}", object)
78
+ end
76
79
 
77
- # Internal: Returns the model name for an object
78
- #
79
- # Example:
80
- # model_name(@post) => "@post"
81
- #
82
- def model_name(object)
83
- item = object.is_a?(Array) ? object.first : object
84
- name = item.class.name.underscore
85
- object.is_a?(Array) ? name.pluralize : name
86
- end
80
+ # Internal: Returns the model name for an object
81
+ #
82
+ # Example:
83
+ # object.class.name # => User
84
+ # object_model_name => "user"
85
+ #
86
+ def object_model_name
87
+ item = object
88
+
89
+ is_collection = item.is_a?(Array)
90
+ item = item.first if is_collection
91
+
92
+ name = item.class.name.underscore
87
93
 
94
+ name = name.pluralize if is_collection
95
+
96
+ name.split("/").last
97
+ end
88
98
  end
89
99
  end
@@ -0,0 +1,87 @@
1
+ module Rabl
2
+ module Sources
3
+ include Helpers
4
+
5
+ # Returns source for a given relative file
6
+ # fetch_source("show", :view_path => "...") => "...contents..."
7
+ def fetch_source(file, options = {})
8
+ view_paths = Array(options[:view_path]) + Array(Rabl.configuration.view_paths)
9
+
10
+ Rabl.source_cache(file, view_paths) do
11
+ file_path = \
12
+ if defined?(Padrino) && context_scope.respond_to?(:settings) && context_scope.respond_to?(:resolve_template)
13
+ fetch_padrino_source(file, options)
14
+ elsif defined?(Rails) && context_scope.respond_to?(:view_paths)
15
+ _view_paths = view_paths + Array(context_scope.view_paths.to_a)
16
+ fetch_rails_source(file, options) || fetch_manual_template(_view_paths, file)
17
+ elsif defined?(Sinatra) && context_scope.respond_to?(:settings)
18
+ fetch_sinatra_source(file, options)
19
+ else # generic template resolution
20
+ fetch_manual_template(view_paths, file)
21
+ end
22
+
23
+ unless File.exist?(file_path.to_s)
24
+ raise "Cannot find rabl template '#{file}' within registered (#{view_paths.map(&:to_s).inspect}) view paths!"
25
+ end
26
+
27
+ [File.read(file_path.to_s), file_path.to_s]
28
+ end
29
+ end
30
+
31
+ private
32
+ # Returns the rabl template path for padrino views using configured views
33
+ def fetch_padrino_source(file, options = {})
34
+ view_path = Array(options[:view_path] || context_scope.settings.views)
35
+
36
+ # use Padrino's own template resolution mechanism
37
+ file_path, _ = context_scope.instance_eval { resolve_template(file) }
38
+
39
+ # Padrino chops the extension, stitch it back on
40
+ File.join(view_path.first.to_s, (file_path.to_s + ".rabl"))
41
+ end
42
+
43
+ # Returns the rabl template path for Rails, including special lookups for Rails 2 and 3
44
+ def fetch_rails_source(file, options = {})
45
+ # use Rails template resolution mechanism if possible (find_template)
46
+ source_format = request_format if defined?(request_format)
47
+
48
+ if source_format && context_scope.respond_to?(:lookup_context) # Rails 3
49
+ lookup_proc = lambda do |partial|
50
+ if ActionPack::VERSION::MAJOR == 3 && ActionPack::VERSION::MINOR < 2
51
+ context_scope.lookup_context.find(file, [], partial)
52
+ else # Rails 3.2 and higher
53
+ # pull format directly from rails unless it is html
54
+ request_format = context_scope.request.format.to_sym
55
+ source_format = request_format unless request_format == :html
56
+ context_scope.lookup_context.find(file, [], partial, [], { :formats => [source_format] })
57
+ end
58
+ end
59
+ template = lookup_proc.call(false) rescue nil
60
+ template ||= lookup_proc.call(true) rescue nil
61
+ template.identifier if template
62
+ elsif source_format && context_scope.respond_to?(:view_paths) # Rails 2
63
+ template = context_scope.view_paths.find_template(file, source_format, false)
64
+ template.filename if template
65
+ end
66
+ end
67
+
68
+ # Returns the rabl template path for sinatra views using configured views
69
+ def fetch_sinatra_source(file, options = {})
70
+ view_path = Array(options[:view_path] || context_scope.settings.views)
71
+ fetch_manual_template(view_path, file)
72
+ end
73
+
74
+ # Returns the rabl template by looking up files within the view_path and specified file path
75
+ def fetch_manual_template(view_path, file)
76
+ Dir[File.join("{#{view_path.join(",")}}", "{#{file},#{partialized(file)}}" + ".{*.,}rabl")].first
77
+ end
78
+
79
+ # Returns a partialized version of a file path
80
+ # partialized("v1/variants/variant") => "v1/variants/_variant"
81
+ def partialized(file)
82
+ partial_file = file.split(File::SEPARATOR)
83
+ partial_file[-1] = "_#{partial_file[-1]}" unless partial_file[-1].start_with?("_")
84
+ partial_file.join(File::SEPARATOR)
85
+ end
86
+ end # Partials
87
+ end # Rabl