Leon's Blogging

Coding blogging for hackers.

用 Concern 來整理 Code

| Comments

當有相當多地方用到同樣的東西時,就可以用 concern 來讓 code 變得更乾淨。

使用時機

  • DRYing up model codes
  • Skin Fat Models.

  • 將可重用的功能抽出來,讓多個 model 共用

  • model 太肥大,將相關的邏輯的 code 放到不同的 concern 裡
  • ActiveSupport::Concern 的風格

ActiveSupport::Concern

任務是讓管理 modules 之間的 dependencies 變得容易。
也可以用 include 同時達成 class methods instance methods (原本必須 include + extend 才能達成)

原本作法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# app/models/concerns/sample.rb
module Sample
  # self.included 會在 Sample 被 include 時執行
  # base 傳入是誰哪個 Class include 了這個 module
  def self.included(base)
    base.class_eval do # 用 class_eval 在該 class 新增 Class method
      #scope :disabled, -> { where(disabled: true) } 可以定義 scope
      #has_many :post 可以定義關聯
      puts("base is #{base}")
      def self.foo
          "這裡是 class 的 method"
      end
    end
  end

  def bar
     "這裡是 instance 的 method"
  end
end
1
2
3
4
5
6
7
8
9
10
#app/models/test.rb
class Test
  include Sample
end

Test.foo
=> "這裡是 class 的 method"

Test.new.bar
=> "這裡是 instance 的 method"

兩個 module 可以互相 include

1
2
3
4
5
6
7
8
# app/models/concerns/sample2.rb
module Sample2
  def self.included(base)
    base.class_eval do
      #do something method
    end
  end
end
1
2
3
4
5
#app/models/test.rb
class Test < ActiveRecord::Base
  include Sample
  # include Sample,再透過 Sample 去 include Sample2 ,這樣就不用一次 include 兩個 module了
end

以上看起來蠻理想的
但因為 include Sample2 的是 Sample
所以 Sample2 的 base 就變成了 Sample , 不是我們要的 Test

更改

1
2
3
4
5
6
7
8
# app/models/concerns/sample.rb
module Sample
  include Sample2
  extend ActiveSupport::Concern
    included do
      self.send(:method) # base 改成 self
    end
end
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
# app/models/concerns/sample2.rb
module Sample2
  extend ActiveSupport::Concern

  #def self.included(base)
  #  base.send(:include, InstanceMethods)
  #  base.extend ClassMethods
  #end

  included do
    self.send(:methods)  # base 改成 self
    # 可以在這裡放當 include 時要執行的東西
    # 可以存取所有 class level 的東西
    # ex1: 宣告 shared scope
    # ex2: 可寫 shared validation
  end

#可以直接定義 ClassMethods 不需再 send(:extend, ClassMethods) 或是用 class_eval 去定義
   module ClassMethods
      def foo
        # do something
      end
   end

#可以直接定義 InstanceMethods 不需再 send(:include, InstanceMethods)或是用 instance_eval 去定義
   module InstanceMethods  #也可以不用特別定義 module InstanceMethods
      def bar
         # do something
      end
   end
end
1
2
3
4
#app/models/test.rb
class Test < ActiveRecord::Base
  include Sample
end

concern vs service object

  • concern
  • 簡單說就是,有許多 model 有共用的邏輯片段,可以拆出來
  • service object
  • 與 concern 不同

  • 兩個搭配使用

  • 將許多 service object 搬到 concern

官方文件:

參考文件:

Comments