重构 MVC; 代码分享工具(重构,改进,打分)
include 模块和 extend 模块的不同:
Class Extension: 通过向singleton class中加入Module来定义class method,是对象扩展的一个特例。
(因为类是特殊的对象。)
例子:
class C; end
module M
def my_method
'a class method'
end
end
#用extend方法: class C; extend M; end
class << C
include M
end
p C.my_method #=> "a class method"
Model和controller.
- 如果 Model 超过 3 个 PageDown(3个屏幕页面放不下),拆 Module
- model可以include进class(增加实例方法), 类可以extend model(增加类方法。)。
- 一个Controller的action如果超过15行,太fat,就应当重构。
- 如果 Controller 每个 action 都有重复的code,使用 before_action
- 如果多个 Controller 有同样的少样几行 action,如 Admin,考虑用 继承inheritance
helper的使用:
Tips 1 : 预先封装会重复写很多次,需要改格式的栏位
标题,图片,按钮(连接),叙述。
例子:
app/helpers/orders_helper.rb
def render_order_created_time(order)
order.created_at.to_s(:short)
end
这样如果以后全站的订单时间格式需要改,那么只要改一处就行了。
Tips 2: 不要 HTML 与 Ruby 混杂写 View
尽量都用 Helper 输出逻辑判断类的代码。
注意:不要过度使用helper
- content_tag标签用了2个以上。
- 有纯html代码
- 有html_safe方法
以上情况可以用partial提炼。
使用 Partial 的原则
- 如果 View 超过 2.5 个 PageDown 请拆 Partial
- 如果元件需要被复用,也是拆 Partial
- 特殊元件可拆 partial
- 登入 / 登出 navbar
- Google Analytics
code climate测试app的代码得分(重构) https://codeclimate.com/
关于Controller的重构:
1. 善用Model scope,对query SQL可以在model层中使用scope定义好它,在controller内直接调用。
2. 用association method build()新建关联对象。
def create
@post = current_user.posts.build(params[:post])
@post.save
end
3.model虚拟属性:
利用在model对某个名字定义一套存取方法,操作没有在数据库中存在的字段,曾为虚拟属性。
class User < ActiveRecord::Base
def full_name
[first_name, last_name].join(' ')
end
def full_name=(name)
split = name.split(' ', 2)
self.first_name = split.first
self.last_name = split.last
end
end
在view中:
#当表格提交时调用了full_name=(XXX)方法,然后得到了first_name和last_name两个参数。
<% form_for @user do |f| %>
<%= f.text_field :full_name %>
<% end %>
在controller🀄️:
class UsersController < ApplicationController
def create
@user = User.create(params[:user])
end
4. 使用model的callback功能,提取controller中的action代码。
情景: 新增文章时,check_box_tag的auto_tagging, 如果打勾表示自动下标签。
重构前: 需要先在action中检查params[:auto_tagging], 然后调用model方法产生标签。
<% form_for @post do |f| %>
<%= f.text_field :content %>
<%= check_box_tag 'auto_tagging' %>
<% end %>
class PostController < ApplicationController
def create
@post = Post.new(params[:post])
if params[:auto_tagging] == '1'
@post.tags = AsiaSearch.generate_tags(@post.content)
else
@post.tags = ""
end
@post.save
end
end
重构后: 新增虚拟属性 auto_tagging, 和一个回调before_save来检查是否自动产生标签。
class Post < ActiveRecord::Base
attr_accessor :auto_tagging
before_save :generate_taggings
private
def generate_taggings
unless auto_tagging == '1' self.tags = Asia.search(self.content)
end
end
<% form_for :note, ... do |f| %>
<%= f.text_field :content %>
<%= f.check_box :auto_tagging %>
<% end %>
class PostController < ApplicationController
def create
@post = Post.new(params[:post])
@post.save
end
end
5. 把action中的逻辑放到Model层,设置一个好名字的方法。在contorll/action中调用这个方法就好。
6. Factory method工厂方法。和5类似,把逻辑都写在model中的一个类方法中。
class Invoice < ActiveRecord::Base
def self.new_by_user(params, user)
invoice = self.new(params)
invoice.address = user.address
invoice.phone = user.phone
invoice.vip = ( invoice.amount > 1000 )
if Time.now.day > 15
invoice.delivery_time = Time.now + 2.month
else
invoice.delivery_time = Time.now + 1.month
end
return invoice
end
end
class InvoiceController < ApplicationController
def create
@invoice = Invoice.new_by_user(params[:invoice], current_user)
@invoice.save
end
end
关于Modle:善用Module抽取相关代码。
如果model层不少有高度相关的代码,希望一眼就可以看到它们是在一起的,或者这些代码可以用在别的Model中。就可以抽取它们放入
app/models/concerns
目录下(类似view的render partial)controller,在 rails 中
app/controllers/concerns
目录就是拿来放 controller 的 module 档案app/models/concerns/has_cellphone.rb
module HasCellphone
def self.included(base)
base.validates_presence_of :cellphone
base.before_save :parse_cellphone
base.extend ClassMethods
end
def parse_cellphone
# do something
end
module ClassMethods
def foobar
# do something
end
end
end
或是使用 Rails ActiveSupport::Concern 语法,可以更简洁一点:
app/models/concerns/has_cellphone.rbmodule HasCellphone
extend ActiveSupport::Concern
included do
validates_presence_of :cellphone
before_save :parse_cellphone
end
def parse_cellphone
# do something
end
class_methods do
def foobar
# do something
end
end
end
最后在 model 里面 include 即可。
class User < ActiveRecord::Base
include HasCellphone
end
关于View
关于 View,最重要的守则就是在 template 中绝对没有商务逻辑。
那到底什么情境适合把代码重构到 Model? 什么时候用 Helper 呢?
如果跟 HTML 画面显示无关,跟商务逻辑有关,则放到 Model 里面。
如果跟 HTML 画面显示有关,则适合放在 Helper 里面。
一般来说,在 Model 里面是不会处理 HTML 代码的,这是 Helper 的事情。
Helper 是 Ruby 代码,里面不适合放太多的 HTML。
如果你有一整段的 HTML 代码想要抽取出来,应该用 Partial 样板。
Partial中使用区域变量代替实例变量。
用全写的: <%= render :partial => "sidebar", :locals => { :post => @post } %>
不要用简写:<%= render :partial => "sidebar" %>
整理Helper档案:
可以整理一下,集中在一个helper中使用,因为和Controller没有对应关系。所有Helper中的方法都是同用的。
分析工具:
- Rubocop 是一个 gem 可以分析 Rails 代码,建议一些可以重构的地方
- CodeClimate 是一个线上的工具,可以为项目评分,并建议哪里需要修改。
补充:
- 重构 这本书是软件开发领域的经典之作,原作使用 Java 代码,但是仍然值得一读。另有 Ruby 版本但没有中文翻译。
- how DHH organizes his rails controllers Rails 发明人 DHH 如何组织 Controller 代码,坚持 RESTful
- Put chubby models on a diet with concerns DHH 对于 concerns 用法的看法
当你仍不满足 Rails 的 concerns 机制时,你会需要更多面向对象的知识,在编程语言教程中有介绍到。关于 Rails 的部分推荐以下补充资料: