12/08/2018, 17:55

ActiveSupport và một số chức năng hữu ích

Giới thiệu Mọi developers sử dụng Rails để xây dựng ứng dụng của mình đều sử dụng Active-Support. Nó mở rộng Rails và mọi lập trình viên có thể đã từng sử dụng nó một lần: ... rails/rails.gemspec 21 s.files = ["README.md"] 22 23 s.add_dependency "activesupport", version 24 ...

Giới thiệu

Mọi developers sử dụng Rails để xây dựng ứng dụng của mình đều sử dụng Active-Support. Nó mở rộng Rails và mọi lập trình viên có thể đã từng sử dụng nó một lần:

... rails/rails.gemspec

  21  s.files = ["README.md"]
  22
  23  s.add_dependency "activesupport", version
  24  s.add_dependency "actionpack",    version
  25  s.add_dependency "actionview",    version

Nhưng thường thì nhiều người không nhận ra nó khi họ sử dụng gem này. Bài viết này gợi lại một số tính năng hữu ích mà Active Support mang lại cho các ứng dụng Rails của chúng ta.

Gem

Active Support là một collection các phần mở rộng cho các lớp Ruby chuẩn. Chủ yếu mục tiêu của chúng là làm việc với Web, nhưng nó cũng có nhiều phần mở rộng hữu ích khác có thể có ích trong việc phát triển các ứng dụng không phải web. Thường thì khi chúng ta tạo ra ứng dụng Rails, hầu hết những thứ tuyệt vời đều đã có sẵn cho chúng ta, bởi vì Active Support bổ sung các phần mở rộng của nó vào các lớp Ruby chuẩn. Tất cả các thay đổi, mà các lớp tiêu chuẩn được tiếp xúc, có thể được xem tại đây.

https://github.com/rails/rails/tree/master/activesupport/lib/active_support/

Hãy cùng xem chi tiết những thay đổi này như thế nào.

Những gì chúng ta đã có

Active Support góp phần chuyển đổi các object thành string. Thay vì dùng ký hiệu Ruby cơ bản với e, chúng ta sẽ nhận được 1 số thực dấu phẩy động.

> require "bigdecimal"
=> true
> BigDecimal.new("0.2").to_s
=> "0.2e0"

rails c
> BigDecimal.new("0.2").to_s
=> "0.2"

Class Range cũng được thay đổi. Phương thức Range.to_s giả định đối số là một trong các định dạng, nhưng giờ chỉ có 1 định dạng có sẵn - :db

> (Date.today..Date.tomorrow).to_s
=> "2018-04-27..2018-04-28"
> (Date.today..Date.tomorrow).to_s(:db)
=> "BETWEEN '2018-04-27' AND '2018-04-28'"

Phương thức Range.include? cũng được các nhà phát triển module chú ý đến bằng cách thêm khả năng pass một range khác làm đối số. Qua đó, nó kiểm tra một range lồng nhau trong một range khác:

ruby > (Date.yesterday..Date.tomorrow).include?((Date.today..Date.tomorrow) ) => true

Chúng ta đều đã biết về phương thức Array.slice, Active Support cũng thêm chức năng này trong Hash, nó hoạt động tương tự như đối với Array, nhưng sử dụng các key thay vì các chỉ mục.

ruby > [1,2,3,4].slice((1..2)) => [2, 3] > {a: 1, b: 2, c: 3}.slice(:a, :c) => {:a=>1, :c=>3}

Những gì chúng ta đã quen

Chúng ta sử dụng phương thức present? và blank? rất nhiều trong code của mình nhưng phương thức này không nằm trong colection tiêu chuẩn của Ruby. Những phương thức này được cung cấp trong ứng dụng Rails của chúng ta nhờ Active Support.

 #...rails/activesupport/lib/active_support/core_ext/object/blank.rb

19 def blank?
20  respond_to?(:empty?) ? !!empty? : !self
21 end

26 def present?
27  !blank?
28 end

Như bạn thấy, đây là một test cho phương thức empty? (phương thức từ Object). Active Support cũng định nghĩa hành vi của các class method cơ bản sau: NilClass, FalseClass, TrueClass, Array, Hash, String, Numeric, Time và một phương thức khác là symbolize_keys, nó sử dụng một phiên bản mở rộng để làm việc với các keys trong Hash: transform_keys

25 def transform_keys!
26   return enum_for(:transform_keys!) { size } unless block_given?
27   keys.each do |key|
28     self[yield(key)] = delete(key)
29   end
30   self
31 end unless method_defined? :transform_keys!

Trong đoạn code trên, chúng ta tạo một key mới bằng cách chạy block trên key cũ, đồng thời lấy giá trị từ key cũ và xóa nó. Nó được sử dụng để cài đặt các thiết kế design pattern, được sử dụng trong chức năng delegate.

#draper/draper.gemspec
    20 s.add_dependency 'activesupport', '~> 5.0'

   # ... rails/activesupport/lib/active_support/core_ext/module/delegation.rb

    157  def delegate(*methods, to: nil, prefix: nil, allow_nil: nil)
    158    unless to
    

Chúng ta nên đề cập ở đây rằng phần mở rộng này không sử dụng chuẩn Module::Forwardable.

Nhờ Active Support, có một "chức năng đối tượng giả" trong Rails (một cái gì đó giống như Hashie::Mash), không có gì kỳ diệu trong Rails configuration - chỉ đơn giản là Hash:

    config.active_storage = ActiveSupport::OrderedOptions.new
    ...
    config.action_mailer = ActiveSupport::OrderedOptions.new
    ...
    config.active_job = ActiveSupport::OrderedOptions.new
    ...
    config.i18n = ActiveSupport::OrderedOptions.new
    ...
    config.i18n.fallbacks = ActiveSupport::OrderedOptions.new
    ...
    config.action_controller = ActiveSupport::OrderedOptions.new
    ...
    config.active_support = ActiveSupport::OrderedOptions.new
    ...
    config.active_record = ActiveSupport::OrderedOptions.new
    ...
    #https://github.com/rails/rails/blob/master/railties/lib/rails/application/configuration.rb
    def method_missing(method, *args)
      if method =~ /=$/
        @configurations[$`.to_sym] = args.first
      else
        @configurations.fetch(method) {
          @configurations[method] = ActiveSupport::OrderedOptions.new
        }
      end
    end

Chỉ là ActiveSupport::OrderedOptions và một chút metaprogramming.

Một đặc điểm khác của Active Support là phương thức try.

    7  def try(*a, &b)
    8    try!(*a, &b) if a.empty? || respond_to?(a.first)
    9  end
    10
    11  def try!(*a, &b)
    12    if a.empty? && block_given?
    13      if b.arity == 0
    14        instance_eval(&b)
    15      else
    16        yield self
    17      end
    18    else
    19      public_send(*a, &b)
    20    end
    21  end

Không có gì khó hiểu về phương thức này. Đối với bất kỳ ai trong số các bạn, giống như tôi, những người không biết rằng arity xác định định lượng và định tính của tập các đối số mà phương thức trả về:

    class C
      def one;    end
      def two(a); end
      def three(*a);  end
      def four(a, b); end
      def five(a, b, *c);    end
      def six(a, b, *c, &d); end
    end
    c = C.new
    c.method(:one).arity     #=> 0
    c.method(:two).arity     #=> 1
    c.method(:three).arity   #=> -1
    c.method(:four).arity    #=> 2
    c.method(:five).arity    #=> -3
    c.method(:six).arity     #=> -3
  # ©https://apidock.com/ruby/Method/arity

Một ví dụ phổ biến khác từ Active Support là vị ngữ in? . và nó tương đương với include? trong Rails. Ngoài ra Ruby không biết phương thức như là cha của module /class. Trong bất kỳ tình huống khó hiểu nào, nó sẽ trả lại Object.

    13 parent_name = name =~ /::[^:]+/ ? $```.freeze : nil
    ...
    35  parent_name ? ActiveSupport::Inflector.constantize(parent_name) : Object

Và Ruby không biết về các lớp con (phương thức lớp con). Để có được thông tin này, bạn phải lật ngược toàn bộ ObjectSpace và nó có rất nhiều resources:

#irb
    > ObjectSpace.each_object(Class){|k| p k.name}
    .....
    => 510

   #rails c
    > ObjectSpace.each_object(Class){|k| p k.name}
    ..........................(Beware – a very long output)
    => 15795

Bạn có biết rằng trong Rails, bạn có thể định nghĩa các thuộc tính của một lớp (không phải biến instance)? Thêm nữa là, bạn có thể làm điều đó, không phải thông qua ký hiệu kinh điển “@@”, mà thông qua class_attribute. Chúng ta sẽ định nghĩa lại chúng trong ví dụ sau và hãy so sánh:

#ruby 
@@default_params = {mime_version: "1.0", charset: "UTF-8", content_type: "text/plain", parts_order: [ "text/plain", "text/enriched", "text/html" ]}.freeze

  class_attribute :default_params
  self.default_params = {mime_version: "1.0",
  charset: "UTF-8",
  content_type: "text/plain",
  parts_order: [ "text/plain", "text/enriched", "text/html" ]}.freeze

Sự khác biệt này là đáng kể. Chúng không chỉ ảnh hưởng đến default_params từ phiên bản như chúng ta muốn mà chúng ta vẫn có thể kế thừa từ lớp cơ sở(base class) và đặt default_params trong lớp dẫn xuất(derived class ) mà không làm hỏng lớp cơ sở.

#irb
    >  class TestClass
    >      @@variable = "var"
    >      def self.variable
    >          # Return the value of this variable
    >           @@variable
    >      end
    >  end
    => :variable
    > TestClass.variable
    => "var"
    > class AnotherClass < TestClass
    >   @@variable = "not var"
    >   def self.variable
    >         # Return the value of this variable
    >          @@variable
    >   end
    > end
    => :variable
    > AnotherClass.variable
    => "not var"
    > TestClass.variable
    => "not var"

với class_attribute điều đó sẽ không xảy ra.

Ngoài ra, phương thức instance_values, instance_variable_names không có trong thư viện ruby cơ sở - những gì về instance_variables (Object method) được định nghĩa trong phần mở rộng.

Module Date có thể giúp bạn có thể tìm thấy những ngày gần nhất theo ngày trong tuần, và ngày trước hoặc sau ngày được chỉ định tại một khoảng thời gian nhất định. Chưa kể đến quan hệ giữa Date và DateTime.

  > date = Date.new(2018, 4, 30)
  => Mon, 30 Apr 2018
  > date.beginning_of_day
  => Mon, 30 Apr 2018 00:00:00 WIB +07:00

Bằng cách này, trong irb dòng date = Date.new (2018, 4, 30) sẽ có một kết quả hoàn toàn khác.

  > date = Date.new(2018, 4, 30)
  => #<Date: 2018-04-30 ((2458239j,0s,0n),+0s,2299161j)>

Đây cũng là Active Support, và đặc biệt là các định nghĩa lại các phương thức inspect và to_s trong lớp Date.

# activesupport/lib/active_support/core_ext/date/conversions.rb

  46  def to_formatted_s(format = :default)
  47   if formatter = DATE_FORMATS[format]
  48     if formatter.respond_to?(:call)
  49       formatter.call(self).to_s
  50     else
  51       strftime(formatter)
  52     end
  53   else
  54     to_default_s
  55   end
  56  end
  57 alias_method :to_default_s, :to_s
  58 alias_method :to_s, :to_formatted_xs

...
  61  def readable_inspect
  62    strftime("%a, %d %b %Y")
  63  end
  64  alias_method :default_inspect, :inspect
  65  alias_method :inspect, :readable_inspect

Và đó mới chỉ là đỉnh của tảng băng trôi. Bạn sẽ không tìm thấy các phương thức truncate, starts_with? và ends_with?, indent trong thư viện cơ sở. Không thể tìm thấy tất cả các nội dung thú vị như dasherize, titleize, underscore, camelize, demodulize, v.v. Bạn có muốn xử lý các params request HTTP không? hãy sử dụng Active Support (phương thức to_param, to_query). Không thể truy cập tới các phần tử của objects collection với from, to. Có các phần mở rộng cho các class Numeric (years, days, hours, seconds, kilobytes, megabytes, gigabytes, exabytes) và nhiều phần bổ sung hữu ích khác. Tìm hiểu thêm về lợi ích của Active Support tại đây.

Link nguồn: http://jetrockets.pro/blog/active-support-outside-inside

0