Post

Rails Application Refactor

Rails Application Refactor

重构现有Rails应用

面临问题

  • 数据库层面单独建立项目,导致产生更新流程, 更改interface, 然后重新启动, Rails应用(这里面存在问题,是不是可以使用listener, 设定脚本,更改 其他项目时候,主动重启Rails应用呢), 也是流程问题, 两个项目interface, rails 分开发行,分支不同
  • 项目趋于复杂, 不同模型,使用字段映射到相同的数据表,然而两个model却趋向于完全不同。导致使用统一model,的validation, callback,变得复杂,混乱。页面中使用同一个controller,出现了各种判断流程。

解决方法

  • « Growing Rails Application in Practice » 中的方法是正确的,
  • 本文实现了一个类似的 class
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
  class CmsModel

    include ActiveModel::Model
    extend ActiveModel::Callbacks

    # 如果更加简单的话,可以使用 method_missing 将全部的方法转发到 model中, 但是这样的话,存在如果保存失败的时候的情况,所以使用当前方法
    DEFAULT_PRO = Proc.new do |name, instance|
      if name != :model
        instance.instance_variable_get("@#{name.to_s}") ||
        (instance.instance_variable_get("@model").present? &&
        instance.instance_variable_get("@model").respond_to?(name) &&
        instance.instance_variable_get("@model").send(name)) ||
        nil
      else
        instance.instance_variable_get("@model")
      end
    end

    def self.cms_attr_reader(* names, &block)
      block ||= DEFAULT_PRO
      names.each do |item|
        define_method item do
          block.call(item, self)
        end
      end
    end

    # save_model 用来在callback中, before_save中, 创建model不能保存为 @model,会导致callback new? 为false
    attr_reader :save_model
    # ActiveModel::Attributes 中的方法, 使model伪装成 active record
    alias :set :assign_attributes

    # 生命callback,统一使用一个save的interface来更新,创建 model, 使用if: :persisted?进行update, create的区分
    define_model_callbacks :save
    define_model_callbacks :init

    def initialize(hash = {})
      _run_init_callbacks do
        set(hash)
      end
    end

    def save
      if valid?
        _run_save_callbacks
      else
        false
      end
    end

    # 供给页面调用,是否 持久化的判断
    def persisted?
      @model
    end

    def new?
      !persisted?
    end

    # 减少delegate 的声明
    def method_missing(method_name, *args)
      if @model && @model.respond_to?(method_name)
        @model.send(method_name, *args)
      else
        super
      end
    end

    # 工具类
    def params_present(*columns)
      (columns.flatten - [:model, :id]).inject({}) do |hash, item|
        infer = instance_variable_get("@#{item.to_s}")
        hash[item] = infer if infer.present?
        hash
      end
    end
  end


  # cms post model

  class CmsPost < CmsModel

    ATTRIBUTES = [:username, :userkey :tags, :model, :id]

    NOT_PARAMS_COLUMN = [:username, :model, :id]

    cms_attr_reader(* ATTRIBUTES)
    attr_writer(* ATTRIBUTES)

    before_save :create_model, if: :new?
    before_save :update_model, unless: :new?

    def initialize(hash = {})
      super
    end

    def model_name
      ActiveModel::Name.new(self, nil, "Post")
    end

    def params
      columns = ATTRIBUTES - NOT_PARAMS_COLUMN
      columns.map do |item|
        [item, instance_variable_get("@#{item.to_s}")]
      end.select {|_, value| value.present?}.to_h.merge(type_id: FORUM_TYPE[:community])
    end



  private

  # validator ------------------------------------
    def userkey_exist
      unless User[:userkey].present?
        errors.add(:userkey, "用户不存在")
      end
    end

  # callback ------------------------------------
  # 这其中不能保存为 model
    def create_model
      # do something
      @save_model = User.create()
    end

    def update_model
      # do something
    end

  end


  # controller

  class PostsController < ApplicationController
    before_action :set_post, only: [:show, :edit, :update, :destroy]

    def new
      @post = CmsPost.new()
    end

    def show
    end

    def edit
    end

    def create
      @post = CmsPost.new(post_params)
      respond_to do |format|
        if @post.save
          format.html { redirect_to @post, notice: '创建成功' }
        else
          format.html {render :new}
        end
      end
    end

    def update
      @post.set(post_params)
      respond_to do |format|
        if @post.save
          format.html { redirect_to @post, notice: 'Post was successfully updated.' }
        else
          format.html { render :edit}
        end
      end
    end

    private

    def set_post
      model = Post.first(id: params[:id])
      @post = CmsPost.new(model: model)
    end
  end

总结

  • 大量的使用rails中自带的现有功能,validatoin, callback, attributes, naming
  • 使用统一的save方法进行更新, 创建操作
  • 使用@model区分更新或者创建, persisted?的返回值
  • 使用 before callback 进行创建更新model, after callback进行关联model的区分
  • 可以通过继承提供大量的工具类型函数, 应用模板方法创建 流程
This post is licensed under CC BY 4.0 by the author.