ActiveSupport::Concern 和 gem 'name_of_person'(300✨) 的內部運行機制分析

理解ActiveRecord::Concern:

參考:include和extend的區別:html

http://www.javashuo.com/article/p-qfmslrbu-ed.htmlgit

 

傳統的模塊看起來像:github

module M
  def self.included(base)
    # base(一個類)擴展了一個模塊"ClassMethods", base的類方法就包含了"ClassMethods"模塊中的方法。
    base.extend ClassMethods
 # base添加了一個:disabled方法。
    base.class_eval do
      scope :disabled, -> { where(disabled: true) }
    end
  end

  module ClassMethods
    ...
  end
end

 

使用ActiveSupport::Concern:app

 

require 'active_support/concern'

module M
  # M擴展了模塊Concern,能夠使用Concern的方法。
  extend ActiveSupport::Concern

  # 當M被一個類包括後,這個類就能夠使用塊內的方法了。
  included do
    scope :disabled, -> { where(disabled: true) }
  end

  # 當M被一個類包括後,這個類的類方法就擴展了,👇的方法就做爲類方法使用。
  class_methods do
    ...
  end
end


 

gem 'name_of_person'

一個小的gem,爲英文網站用戶的註冊名字添加了不少調用的方法。less

https://github.com/basecamp/name_of_person/tree/master/lib/name_of_person網站

  1. 加載了gem後,
  2. ActiveRecord::Base包含了模塊HasPersonName, 就能夠使用lib/name_of_person/has_person_name.rb中的方法:類方法has_person_name.
  3. 在Rails app中, app/model/user.rb, 使用has_person_name方法後,就include包含了模塊Assignable
  4. User的實例就新增了2個實例方法,這兩個方法會調用模塊PersonName中的方法
    • @user.name=: 調用PersonName.full(name)方法,@user的first_name, last_name屬性被分配值。
    • @user.name:  返回一個PersonName.new對象,這個對象能夠使用:
      • full | initials | familiar 等定義在模塊PersonName中的方法。
      • first | last

使用方法:

1 . User類必須包括first_name, last_name2個屬性,添加validates :first_name, :last_name, presence: trueui

2. 當實例化一個@user時,代碼內部調用name= 方法爲first_name, last_name屬性分配值!spa

(這裏不是很理解,是不是devise這個gem,當發現必須驗證first_name, last_name後,自動調用name=方法?)

3. 以後經過@user.name.xxx就能夠使用不一樣的名和姓的組合。code

 

分析:先看三張圖:

圖2htm

 

 

圖3:

 

 

 

@user.name的內部運行機制:

首先是一個判斷:

if @user.first_name
  NameOfPerson::PersonName.new(@user.first_name, @user.last_name)
end

若是first_name存在,則新增一個PersonName對象,調用initialize方法

    def initialize(first, last = nil)
      raise ArgumentError, "First name is required" unless first.present?
      @first, @last = first, last
      super full
    end

而後調用full這個方法,進行if判斷

    def full
      @full ||= last.present? ? "#{first} #{last}" : first
    end
分析:
若是@user.last_name存在(last.present?),則 把@user的兩個name屬性合併,並分配給@full對象。

最後返回一個PersonName對象實例, 內部包括@first, @full, 及@last(根據@user決定是否存在)

 

@user.name = "Dav Tom"內部運行分析:

    def name=(name)
      full_name = NameOfPerson::PersonName.full(name)
      self.first_name, self.last_name = full_name.try(:first), full_name.try(:last)
    end

 

首先:調用模塊PersonName的類方法full。

  • 把傳入的字符串參數分紅first, last變量
  • 若是first變量存在,則新建一個PersonName對象
  • 以後的分析和@ueser.name相同。 
    def self.full(full_name)
      first, last = full_name.to_s.strip.split(/\s+/, 2)
      new(first, last) if first.present?
    end
相關文章
相關標籤/搜索