给Rails程序添加主题切换功能
都明白是什么意思的,比如像Wordpress一样的主题切换,或者说皮肤?不管怎么个叫法,在Rails里实现这种功能是很简单的。
我最近在做Android开发,我们的团队需要一个门面吧,继续使用我自己的博客程序,它可是支持主题切换功能的哟。看我为植物人新做的一个主题
点图片可以看大图的。我们是騎乘草泥马远征马勒戈壁的植物人!
展示完成,讲一下是怎么简单实现的
下面的代码基于Rails 3,但基本适用于其他版本,除了Rails代码可能不一样
原理上其实很简单,controller在接受到访问請求的最后会render一个模版。那这个模版在哪?
Rails默认模版位置显然是app/views目录下以相应的controller命名的文件夹。我们要做的就是改变它!!!这一切都存在于一个view_paths变量中。
Rails总是在Convention的同时给我们Configuration的自由,借助于controller的prepend_view_path方法,我们可以轻松的在模版查找路径之前加一个新的路径。
假如我们的主题放在app/themes/abitno里,那我们只需要在controller加一个类似下面的before_filter,你的主题文件就优先被render了!
prepend_view_path(File.join(Rails.root, "app/themes/abitno"))
很简单吧?哈哈,如果你不关心性能问题,那一切都结束了。
究竟会有什么性能问题?虽然我的小博客不太在乎,但是发现问题就得解决掉,这可是做一个优秀软件、做一个像我这样的优秀程序员的根本呀==!
性能问题的根源在于view_paths并不是简单的字符串数组
irb(main):020:0> vp = ApplicationController.view_paths irb(main):021:0> vp.class => ActionView::PathSet irb(main):022:0> vp.first.class => ActionView::FileSystemResolver
若只是简单的一个ActionView::PathSet倒也没什么大不了,我们来看Rails在运行过程中是怎样通过view_paths来查找模版的。得从ActionView::LookupContext说起
LookupContext is the object responsible to hold all information required to lookup templates, i.e. view paths and details. The LookupContext is also responsible to generate a key, given to view paths, used in the resolver cache lookup. Since this key is generated just once during the request, it speeds up all cache accesses.
从它的描述里可以明显看到它把对模版的查询作了cache,让我们顺着它的find方法看它都会cache哪些东西
def view_paths=(paths) @view_paths = ActionView::Base.process_view_paths(paths) end def find(name, prefix = nil, partial = false) @view_paths.find(*args_for_lookup(name, prefix, partial)) end
于是查看ActionView::Base.process_view_paths作了什么
def self.process_view_paths(value) ActionView::PathSet.new(Array.wrap(value)) end
原来只是生成了一个ActionView::PathSet对象,我们离目标很近了
def find(path, prefix = nil, partial = false, details = {}, key = nil) template = find_all(path, prefix, partial, details, key).first raise MissingTemplate.new(self, "#{prefix}/#{path}", details, partial) unless template template end def find_all(*args) each do |resolver| templates = resolver.find_all(*args) return templates unless templates.empty? end [] end
PathSet是Array,上面的find_all显然是对自身的每一个元素执行了find_all,我们来看它的元素究竟是什么属性
def typecast! each_with_index do |path, i| next unless path.is_a?(String) self[i] = FileSystemResolver.new(path) end end
相信我,这次确实是离真相很近了!我们终于在ActionView::FileSystemResolver中找到了cache所在,泪流满面啊TAT
def find_all(name, prefix=nil, partial=false, details={}, key=nil) cached(key, prefix, name, partial) do find_templates(name, prefix, partial, details) end end
原来是要进行这么一项操作
def query(path, exts, formats) query = File.join(@path, path) exts.each do |ext| query << '{' << ext.map {|e| e && ".#{e}" }.join(',') << ',}' end Dir[query].reject { |p| File.directory?(p) }.map do |p| handler, format = extract_handler_and_format(p, formats) Template.new(File.read(p), File.expand_path(p), handler, :virtual_path => path, :format => format) end end
是的,你没有看错,我们进行文件读取生成了一个Template,这难道不是性能问题吗?
秉承伟大的无产阶级革命精神,发现问题然后就要解决问题
我们要做的也很简单,学习Rails这种策略,本着lazy和cache精神
@cached[theme_name] ||= ActionView::Base.process_view_paths(theme_path) self.view_paths = ActionController::Base.view_paths.dup.unshift @cached[theme_name]
如是而已,被我说了这么一通,我果然还是啰嗦型的么~~
假如忍心忽略这一点性能上的损失,就可以在你修改主题文件后即时生效了。不过就算不忽略这一性能问题,我们依然有方法让主题修改后即时生效,不是吗?
本文基于 署名-非商业性使用-禁止演绎 2.5 中国大陆 发布
1 COMMENTS >>LEAVE<<
-
好勤奋~可惜我不是学这个~加油!
