rabl 0.11.1 → 0.11.2

Sign up to get free protection for your applications and to get access to all the features.
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