Rails源码研究之ActionController:一,基本架构、render、redirect

1,action_controller.rb:
[code]
$:.unshift(File.dirname(__FILE__)) unless
$:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))

unless defined?(ActiveSupport)
begin
$:.unshift(File.dirname(__FILE__) + "/../../activesupport/lib")
require 'active_support'
rescue LoadError
require 'rubygems'
gem 'activesupport'
end
end

require 'action_controller/base'
require 'action_controller/deprecated_redirects'
require 'action_controller/request'
require 'action_controller/deprecated_request_methods'
require 'action_controller/rescue'
require 'action_controller/benchmarking'
require 'action_controller/flash'
require 'action_controller/filters'
require 'action_controller/layout'
require 'action_controller/deprecated_dependencies'
require 'action_controller/mime_responds'
require 'action_controller/pagination'
require 'action_controller/scaffolding'
require 'action_controller/helpers'
require 'action_controller/cookies'
require 'action_controller/cgi_process'
require 'action_controller/caching'
require 'action_controller/verification'
require 'action_controller/streaming'
require 'action_controller/session_management'
require 'action_controller/components'
require 'action_controller/macros/auto_complete'
require 'action_controller/macros/in_place_editing'

require 'action_view'
ActionController::Base.template_class = ActionView::Base

ActionController::Base.class_eval do
include ActionController::Flash
include ActionController::Filters
include ActionController::Layout
include ActionController::Benchmarking
include ActionController::Rescue
include ActionController::Dependencies
include ActionController::MimeResponds
include ActionController::Pagination
include ActionController::Scaffolding
include ActionController::Helpers
include ActionController::Cookies
include ActionController::Caching
include ActionController::Verification
include ActionController::Streaming
include ActionController::SessionManagement
include ActionController::Components
include ActionController::Macros::AutoComplete
include ActionController::Macros::InPlaceEditing
end
[/code]
和active_record.rb很类似,加载当前文件,加载activesupport,加载一坨一坨的组件

2,base.rb:
[code]
require 'action_controller/mime_type'
require 'action_controller/request'
require 'action_controller/response'
require 'action_controller/routing'
require 'action_controller/resources'
require 'action_controller/url_rewriter'
require 'action_controller/status_codes'
require 'drb'
require 'set'

module ActionController

class Base
DEFAULT_RENDER_STATUS_CODE = "200 OK"

include StatusCodes

@@default_charset = "utf-8"
cattr_accessor :default_charset

class_inheritable_accessor :template_root

cattr_accessor :logger

attr_internal :request

attr_internal :params

attr_internal :response

attr_internal :session

attr_internal :headers

attr_accessor :action_name

class << self

def controller_class_name
@controller_class_name ||= name.demodulize
end

def controller_name
@controller_name ||= controller_class_name.sub(/Controller$/, '').underscore
end

end

public

def process(request, response, method = :perform_action, *arguments)
initialize_template_class(response)
assign_shortcuts(request, response)
initialize_current_url
assign_names
forget_variables_added_to_assigns

log_processing
send(method, *arguments)

assign_default_content_type_and_charset
response
ensure
process_cleanup
end

def url_for(options = {}, *parameters_for_method_reference)
case options
when String
options

when Symbol
ActiveSupport::Deprecation.warn(
"You called url_for(:#{options}), which is a deprecated API call. Instead you should use the named " +
"route directly, like #{options}(). Using symbols and parameters with url_for will be removed from Rails 2.0.",
caller
)

send(options, *parameters_for_method_reference)

when Hash
@url.rewrite(rewrite_options(options))
end
end

def controller_class_name
self.class.controller_class_name
end

def controller_name
self.class.controller_name
end

def session_enabled?
request.session_options && request.session_options[:disabled] != false
end

protected

def render(options = nil, deprecated_status = nil, &block)
raise DoubleRenderError, "Can only render or redirect once per action" if performed?

if options.nil?
return render_file(default_template_name, deprecated_status, true)
else
unless options.is_a?(Hash)
if options == :update
options = { :update => true }
else
ActiveSupport::Deprecation.warn(
"You called render('#{options}'), which is a deprecated API call. Instead you use " +
"render :file => #{options}. Calling render with just a string will be removed from Rails 2.0.",
caller
)

return render_file(options, deprecated_status, true)
end
end
end

if content_type = options[:content_type]
response.content_type = content_type.to_s
end

if text = options[:text]
render_text(text, options[:status])

else
if file = options[:file]
render_file(file, options[:status], options[:use_full_path], options[:locals] || {})

elsif template = options[:template]
render_file(template, options[:status], true)

elsif inline = options[:inline]
render_template(inline, options[:status], options[:type], options[:locals] || {})

elsif action_name = options[:action]
ActiveSupport::Deprecation.silence do
render_action(action_name, options[:status], options[:layout])
end

elsif xml = options[:xml]
render_xml(xml, options[:status])

elsif json = options[:json]
render_json(json, options[:callback], options[:status])

elsif partial = options[:partial]
partial = default_template_name if partial == true
if collection = options[:collection]
render_partial_collection(partial, collection, options[:spacer_template], options[:locals], options[:status])
else
render_partial(partial, ActionView::Base::ObjectWrapper.new(options[:object]), options[:locals], options[:status])
end

elsif options[:update]
add_variables_to_assigns
@template.send :evaluate_assigns

generator = ActionView::Helpers::PrototypeHelper::JavaScriptGenerator.new(@template, &block)
render_javascript(generator.to_s)

elsif options[:nothing]
render_text(" ", options[:status])

else
render_file(default_template_name, options[:status], true)

end
end
end

def render_action(action_name, status = nil, with_layout = true)
template = default_template_name(action_name.to_s)
if with_layout && !template_exempt_from_layout?(template)
render_with_layout(:file => template, :status => status, :use_full_path => true, :layout => true)
else
render_without_layout(:file => template, :status => status, :use_full_path => true)
end
end

def render_file(template_path, status = nil, use_full_path = false, locals = {})
add_variables_to_assigns
assert_existence_of_template_file(template_path) if use_full_path
logger.info("Rendering #{template_path}" + (status ? " (#{status})" : '')) if logger
render_text(@template.render_file(template_path, use_full_path, locals), status)
end

def render_template(template, status = nil, type = :rhtml, local_assigns = {})
add_variables_to_assigns
render_text(@template.render_template(type, template, nil, local_assigns), status)
end

def render_text(text = nil, status = nil, append_response = false)
@performed_render = true

response.headers['Status'] = interpret_status(status || DEFAULT_RENDER_STATUS_CODE)

if append_response
response.body ||= ''
response.body << text
else
response.body = text
end
end

def render_javascript(javascript, status = nil, append_response = true)
response.content_type = Mime::JS
render_text(javascript, status, append_response)
end

def render_xml(xml, status = nil)
response.content_type = Mime::XML
render_text(xml, status)
end

def render_json(json, callback = nil, status = nil)
json = "#{callback}(#{json})" unless callback.blank?

response.content_type = Mime::JSON
render_text(json, status)
end

def render_nothing(status = nil)
render_text(' ', status)
end

def render_partial(partial_path = default_template_name, object = nil, local_assigns = nil, status = nil)
add_variables_to_assigns
render_text(@template.render_partial(partial_path, object, local_assigns), status)
end

def render_partial_collection(partial_name, collection, partial_spacer_template = nil, local_assigns = nil, status = nil)
add_variables_to_assigns
render_text(@template.render_partial_collection(partial_name, collection, partial_spacer_template, local_assigns), status)
end

def render_with_layout(template_name = default_template_name, status = nil, layout = nil)
render_with_a_layout(template_name, status, layout)
end

def render_without_layout(template_name = default_template_name, status = nil)
render_with_no_layout(template_name, status)
end


def head(*args)
if args.length > 2
raise ArgumentError, "too many arguments to head"
elsif args.empty?
raise ArgumentError, "too few arguments to head"
elsif args.length == 2
status = args.shift
options = args.shift
elsif args.first.is_a?(Hash)
options = args.first
else
status = args.first
options = {}
end

raise ArgumentError, "head requires an options hash" if !options.is_a?(Hash)

status = interpret_status(status || options.delete(:status) || :ok)

options.each do |key, value|
headers[key.to_s.dasherize.split(/-/).map { |v| v.capitalize }.join("-")] = value.to_s
end

render :nothing => true, :status => status
end

def erase_render_results
response.body = nil
@performed_render = false
end

def erase_redirect_results
@performed_redirect = false
response.redirected_to = nil
response.redirected_to_method_params = nil
response.headers['Status'] = DEFAULT_RENDER_STATUS_CODE
response.headers.delete('Location')
end

def erase_results
erase_render_results
erase_redirect_results
end

def redirect_to(options = {}, *parameters_for_method_reference)
case options
when %r{^\w+://.*}
raise DoubleRenderError if performed?
logger.info("Redirected to #{options}") if logger
response.redirect(options)
response.redirected_to = options
@performed_redirect = true

when String
redirect_to(request.protocol + request.host_with_port + options)

when :back
request.env["HTTP_REFERER"] ? redirect_to(request.env["HTTP_REFERER"]) : raise(RedirectBackError)

else
if parameters_for_method_reference.empty?
redirect_to(url_for(options))
response.redirected_to = options
else
redirect_to(url_for(options, *parameters_for_method_reference))
response.redirected_to, response.redirected_to_method_params = options, parameters_for_method_reference
end
end
end

def expires_in(seconds, options = {})
cache_options = { 'max-age' => seconds, 'private' => true }.symbolize_keys.merge!(options.symbolize_keys)
cache_options.delete_if { |k,v| v.nil? or v == false }
cache_control = cache_options.map{ |k,v| v == true ? k.to_s : "#{k.to_s}=#{v.to_s}"}
response.headers["Cache-Control"] = cache_control.join(', ')
end

def expires_now
response.headers["Cache-Control"] = "no-cache"
end

def reset_session
request.reset_session
@_session = request.session
response.session = @_session
end

private
def perform_action
if self.class.action_methods.include?(action_name)
send(action_name)
render unless performed?
elsif respond_to? :method_missing
send(:method_missing, action_name)
render unless performed?
elsif template_exists? && template_public?
render
else
raise UnknownAction, "No action responded to #{action_name}", caller
end
end

end
end
[/code]
节选了部分代码,调用流程为process -> perform_action => action || method_missing || template || raise UnknownAction
其中需要注意的几点:
1,@@default_charset = "utf-8"
2,attr_internal的属性有request、params、response、session、headers
3,cattr_accessor的属性有default_charset、logger、consider_all_requests_local等
catter_accessor标识的是Class Attributes,见这篇Blog[url=http://drnicwilliams.com/2006/08/27/so-cattr_accessor-doesnt-work-like-it-should/]So, cattr_accessor doesn’t work like it should?[/url]
catter_accessor方法的定义在active_support\core_ext\class\attribute_accessors.rb:
[code]
class Class
def cattr_reader(*syms)
syms.flatten.each do |sym|
next if sym.is_a?(Hash)
class_eval(<<-EOS, __FILE__, __LINE__)
unless defined? @@#{sym}
@@#{sym} = nil
end

def self.#{sym}
@@#{sym}
end

def #{sym}
@@#{sym}
end
EOS
end
end

def cattr_writer(*syms)
options = syms.last.is_a?(Hash) ? syms.pop : {}
syms.flatten.each do |sym|
class_eval(<<-EOS, __FILE__, __LINE__)
unless defined? @@#{sym}
@@#{sym} = nil
end

def self.#{sym}=(obj)
@@#{sym} = obj
end

#{"
def #{sym}=(obj)
@@#{sym} = obj
end
" unless options[:instance_writer] == false }
EOS
end
end

def cattr_accessor(*syms)
cattr_reader(*syms)
cattr_writer(*syms)
end
end
[/code]
4,render方法的参数有:
1)nil -> render_file
2):text -> render_text
3):file -> render_file
4):template -> render_file
5):inline -> render_template
6):action -> render_action
7):xml -> render_xml
8):json -> render_json
9):partial -> render_partial || render_partial_collection
10):update -> render_javascript
11):noting -> render_text
5,redirect_to方法的参数有:
1)Hash -> redirect_to(url_for(options))
[code]
redirect_to :action => "show", :id => 5
[/code]
2)URL -> response.redirect(options)
[code]
redirect_to "http://www.rubyonrails.org"
[/code]
3)String -> redirect_to(request.protocol + request.host_with_port + options)
[code]
redirect_to "/images/screenshot.jpg"
[/code]
4):back -> redirect_to(request.env["HTTP_REFERER"])
[code]
redirect_to :back
[/code]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值