1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
|
require 'set'
module ActionController
# See <tt>Renderers.add</tt>
def self.add_renderer(key, &block)
Renderers.add(key, &block)
end
# See <tt>Renderers.remove</tt>
def self.remove_renderer(key)
Renderers.remove(key)
end
class MissingRenderer < LoadError
def initialize(format)
super "No renderer defined for format: #{format}"
end
end
module Renderers
extend ActiveSupport::Concern
included do
class_attribute :_renderers
self._renderers = Set.new.freeze
end
module ClassMethods
def use_renderers(*args)
renderers = _renderers + args
self._renderers = renderers.freeze
end
alias use_renderer use_renderers
end
def render_to_body(options)
_handle_render_options(options) || super
end
def _handle_render_options(options)
_renderers.each do |name|
if options.key?(name)
_process_options(options)
return send("_render_option_#{name}", options.delete(name), options)
end
end
nil
end
# A Set containing renderer names that correspond to available renderer procs.
# Default values are <tt>:json</tt>, <tt>:js</tt>, <tt>:xml</tt>.
RENDERERS = Set.new
# Adds a new renderer to call within controller actions.
# A renderer is invoked by passing its name as an option to
# <tt>AbstractController::Rendering#render</tt>. To create a renderer
# pass it a name and a block. The block takes two arguments, the first
# is the value paired with its key and the second is the remaining
# hash of options passed to +render+.
#
# Create a csv renderer:
#
# ActionController::Renderers.add :csv do |obj, options|
# filename = options[:filename] || 'data'
# str = obj.respond_to?(:to_csv) ? obj.to_csv : obj.to_s
# send_data str, type: Mime::CSV,
# disposition: "attachment; filename=#{filename}.csv"
# end
#
# Note that we used Mime::CSV for the csv mime type as it comes with Rails.
# For a custom renderer, you'll need to register a mime type with
# <tt>Mime::Type.register</tt>.
#
# To use the csv renderer in a controller action:
#
# def show
# @csvable = Csvable.find(params[:id])
# respond_to do |format|
# format.html
# format.csv { render csv: @csvable, filename: @csvable.name }
# end
# end
# To use renderers and their mime types in more concise ways, see
# <tt>ActionController::MimeResponds::ClassMethods.respond_to</tt> and
# <tt>ActionController::MimeResponds#respond_with</tt>
def self.add(key, &block)
define_method("_render_option_#{key}", &block)
RENDERERS << key.to_sym
end
# This method is the opposite of add method.
#
# Usage:
#
# ActionController::Renderers.remove(:csv)
def self.remove(key)
RENDERERS.delete(key.to_sym)
method = "_render_option_#{key}"
remove_method(method) if method_defined?(method)
end
module All
extend ActiveSupport::Concern
include Renderers
included do
self._renderers = RENDERERS
end
end
add :json do |json, options|
json = json.to_json(options) unless json.kind_of?(String)
if options[:callback].present?
if self.content_type.nil? || self.content_type == Mime::JSON
self.content_type = Mime::JS
end
"#{options[:callback]}(#{json})"
else
self.content_type ||= Mime::JSON
json
end
end
add :js do |js, options|
self.content_type ||= Mime::JS
js.respond_to?(:to_js) ? js.to_js(options) : js
end
add :xml do |xml, options|
self.content_type ||= Mime::XML
xml.respond_to?(:to_xml) ? xml.to_xml(options) : xml
end
end
end
|