您的位置:首页 > 大数据 > 人工智能

rails源码解读之ActionView之画面标签

2011-05-09 17:01 204 查看
rails的画面标签虽说已经基本够用了,

但是总也会有实现起来不方便的地方,

这时候就需要用到自定义标签了。

 

为了防止盗链,先把原文地址贴出来

http://blog.csdn.net/zhao_hongsheng/archive/2011/05/09/6406996.aspx

想要自定义标签就得能看懂rails的源码。

(在网上搜索了一下,中文资料太少了,还是自己看吧)

 

写出来各位网友分享一下。

rails版本2.3.8
path: rails/actionpack/lib/action_view/helpers

相关文件 
form_helper.rb

form_tag_helper.rb

form_options_helper.rb

tag_helper.rb

form_helper.rb
主要从2标签入手form_tag,form_for
其他就好办了。

看源码,先读注释!!

 

在form_helper.rb开头看到

# There are two types of form helpers: those that specifically work with model attributes and those that don't.
# This helper deals with those that work with model attributes; to see an example of form helpers that don't work
# with model attributes, check the ActionView::Helpers::FormTagHelper documentation.
 

说明form_tag标签用到的方法基本都定义在form_tag_helper.rb文件中

form_for标签用到的方法都定义在form_helper.rb文件中。

 

先看个简单的form_tag_helper.rb

打开一看,所有用的方法都定义在里面了。

 

 

比如这个text_field_tag标签,

里面调用tag_helper.rb中的tag方法实现了标签的输出。

 

def text_field_tag(name, value = nil, options = {})
tag :input, { "type" => "text", "name" => name, "id" => name, "value" => value }.update(options.stringify_keys)
end


很简单嘛~,但是实际中大多用的还是form_for标签。

这个用点儿小难度,follow me ~

 

打开form_helpler.rb文件,找到form_for的定义

def form_for(record_or_name_or_array, *args, &proc)
raise ArgumentError, "Missing block" unless block_given?
options = args.extract_options!
case record_or_name_or_array
when String, Symbol
object_name = record_or_name_or_array
when Array
object = record_or_name_or_array.last
object_name = ActionController::RecordIdentifier.singular_class_name(object)
apply_form_for_options!(record_or_name_or_array, options)
args.unshift object
else
object = record_or_name_or_array
object_name = ActionController::RecordIdentifier.singular_class_name(object)
apply_form_for_options!([object], options)
args.unshift object
end
concat(form_tag(options.delete(:url) || {}, options.delete(:html) || {}), proc.binding)
fields_for(object_name, *(args << options), &proc)
concat('</form>', proc.binding)
end


这句先把<form action="xxxx" method="xx" ....>等内容生成好

concat(form_tag(options.delete(:url) || {}, options.delete(:html) || {}), proc.binding)
 

然后关键的调用了个fields_for方法,来生成form标签里面的内容

找到这个方法的定义,还是在这个文件里

def fields_for(record_or_name_or_array, *args, &block)
raise ArgumentError, "Missing block" unless block_given?
options = args.extract_options!
case record_or_name_or_array
when String, Symbol
object_name = record_or_name_or_array
object = args.first
when Array
object = record_or_name_or_array.last
object_name = ActionController::RecordIdentifier.singular_class_name(object)
apply_form_for_options!(record_or_name_or_array, options)
else
object = record_or_name_or_array
object_name = ActionController::RecordIdentifier.singular_class_name(object)
end
builder = options[:builder] || ActionView::Base.default_form_builder
yield builder.new(object_name, object, self, options, block)
end
 

 

注意最后2行,实例化了一个ActionView::Base.default_form_builder的对象,

这个对象就是我们经常用到的

<% form_for :ad, :url=>{:action => 'ad_create', :id => @campaign} do |f| %>
<% end %>
 

中的 f 变量了,

那么这个f 到底是什么类的对象呢?

看form_helper.rb的最后

class Base
cattr_accessor :default_form_builder
self.default_form_builder = ::ActionView::Helpers::FormBuilder
end
 

原来是ActionView::Helpers::FormBuilder类型的对象。

那么 <%= f.text_field .....%>就是调用的

FormBuilder类中的相应的text_field方法喽~

 

有代码为证,这个类里确实定义了这些方法

class FormBuilder #:nodoc:
# The methods which wrap a form helper call.
class_inheritable_accessor :field_helpers
self.field_helpers = (FormHelper.instance_methods - ['form_for'])
attr_accessor :object_name, :object, :options
def initialize(object_name, object, template, options, proc)
@object_name, @object, @template, @options, @proc = object_name, object, template, options, proc
@default_options = @options ? @options.slice(:index) : {}
end
(field_helpers - %w(label check_box radio_button fields_for)).each do |selector|
src = <<-end_src
def #{selector}(method, options = {})
@template.send(#{selector.inspect}, @object_name, method, objectify_options(options))
end
end_src
class_eval src, __FILE__, __LINE__
end
def fields_for(record_or_name_or_array, *args, &block)
case record_or_name_or_array
when String, Symbol
name = "#{object_name}[#{record_or_name_or_array}]"
when Array
object = record_or_name_or_array.last
name = "#{object_name}[#{ActionController::RecordIdentifier.singular_class_name(object)}]"
args.unshift(object)
else
object = record_or_name_or_array
name = "#{object_name}[#{ActionController::RecordIdentifier.singular_class_name(object)}]"
args.unshift(object)
end
@template.fields_for(name, *args, &block)
end
def label(method, text = nil, options = {})
@template.label(@object_name, method, text, objectify_options(options))
end
def check_box(method, options = {}, checked_value = "1", unchecked_value = "0")
@template.check_box(@object_name, method, objectify_options(options), checked_value, unchecked_value)
end
def radio_button(method, tag_value, options = {})
@template.radio_button(@object_name, method, tag_value, objectify_options(options))
end
def error_message_on(method, prepend_text = "", append_text = "", css_class = "formError")
@template.error_message_on(@object, method, prepend_text, append_text, css_class)
end
def error_messages(options = {})
@template.error_messages_for(@object_name, objectify_options(options))
end
def submit(value = "Save changes", options = {})
@template.submit_tag(value, options.reverse_merge(:id => "#{object_name}_submit"))
end
private
def objectify_options(options)
@default_options.merge(options.merge(:object => @object))
end
end
end
 

这里要注意的是

(field_helpers - %w(label check_box radio_button fields_for)).each do |selector|
src = <<-end_src
def #{selector}(method, options = {})
@template.send(#{selector.inspect}, @object_name, method, objectify_options(options))
end
end_src
class_eval src, __FILE__, __LINE__
end
 

又是class_eval,ruby的黑魔法(black magic)

除了label check_box radio_button fields_for这四个方法

所有form_helper module里的实例方法都被重新定义了,

当然这四个方法随后也都被重写了,

之所以分开写只程序技巧上的问题,

但却增加了阅读的难度。

 

下面具体看这些方法是怎么被重写的。

def #{selector}(method, options = {})
@template.send(#{selector.inspect}, @object_name, method, objectify_options(options))
end
 

@template.send,调用了@template这个对象的相应的方法,

可以看出其实FormBuilder中的这些方法只是做了一个代理,

终究还是调用了,FormHelper中定义的方法,

好处就是f.text_field的时候不用传@object这个参数了。

 

@template就是self,也就是Active::View::Base的实例对象,

没看懂?仔细看看FormBuilder的initialize方法,你行的!

 

现在问题简化了,只要去看FormHelper中关于标签的方法如何定义的就好啦,

拿text_field举例

def file_field(object_name, method, options = {})
InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_input_field_tag("file", options)
end
 

调用了,InstanceTag类的to_input_field_tag方法,

to_input_field_tag中还是调用tag_helper.rb中(Tag类)的tag方法输出标签。

def to_input_field_tag(field_type, options = {})
options = options.stringify_keys
options["size"] = options["maxlength"] || DEFAULT_FIELD_OPTIONS["size"] unless options.key?("size")
options = DEFAULT_FIELD_OPTIONS.merge(options)
if field_type == "hidden"
options.delete("size")
end
options["type"] = field_type
options["value"] ||= value_before_type_cast(object) unless field_type == "file"
options["value"] &&= html_escape(options["value"])
add_default_name_and_id(options)
tag("input", options)
end
 

到这里就已经理清了整个标签输出的过程。

之后标签自定义什么的,随你了~

 

 

顺便提一下,标签输出的ActiveRecord值都是before_type_cast的

before_type_cast是什么不知道?那得补一下ActiveRecord相关知识了,

有空儿我再写上来。

options["value"] ||= value_before_type_cast(object) unless field_type == "file"
 

 

 

也许还有的网友跟我有同样的疑问
 select方法的定义在FormBuilder里怎么找不到?

用IDE在全局搜索下,发现在form_options_helper.rb下还有定义。。

class FormBuilder
def select(method, choices, options = {}, html_options = {})
@template.select(@object_name, method, choices, options.merge(:object => @object), html_options)
end
def collection_select(method, collection, value_method, text_method, options = {}, html_options = {})
@template.collection_select(@object_name, method, collection, value_method, text_method, options.merge(:object => @object), html_options)
end
def country_select(method, priority_countries = nil, options = {}, html_options = {})
@template.country_select(@object_name, method, priority_countries, options.merge(:object => @object), html_options)
end
def time_zone_select(method, priority_zones = nil, options = {}, html_options = {})
@template.time_zone_select(@object_name, method, priority_zones, options.merge(:object => @object), html_options)
end
end
 

完。。。

 

第一次写源码解读,若有错误,欢迎指正探讨~
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息