重构 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的地方:

  • HTML 与Ruby 高度混杂
  • 该段程式码有很多 if / else
  • 该段程式码衣服穿很多层 simple_format(truncate(auto_link(@post.content), :length => 30) )
 注意:不要过度使用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.rb
module 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 是一个线上的工具,可以为项目评分,并建议哪里需要修改。
 
补充:

当你仍不满足 Rails 的 concerns 机制时,你会需要更多面向对象的知识,在编程语言教程中有介绍到。关于 Rails 的部分推荐以下补充资料:

 
 
 
 
posted @ 2018-08-02 18:23  Mr-chen  阅读(458)  评论(0编辑  收藏  举报