Computer >> Hướng Dẫn Máy Tính >  >> Lập Trình >> Ruby

Làm chủ Mixins và Modules trong Ruby on Rails: Hướng dẫn Thực tế

Modules và mixins là,毫无疑 doubte, tài nguyên tuyệt vời làm cho Ruby hấp dẫn. Chúng cho ứng dụng khả năng share code dùng dễ dàng ở nơi khác. Nó cũng giúp organize code bằng cách group functionality và concerns, cải thiện readability và maintainability.

Trong bài này, chúng ta sẽ đi qua concept đằng sau modules và mixins. Chúng ta sẽ học cách tạo và mix module vào class khác và thảo luận benefit của dùng chúng trong Rails application.

Tôi hy vọng bạn sẽ enjoy hành trình!

Module Là Gì

Modules là một trong những tài nguyên sáng bóng nhất của Ruby vì chúng cung cấp hai benefit lớn: chúng ta có thể tạo namespaces để prevent name clashes và dùng chúng as mixins để share code across application.

Về mặt structural, module khá giống bất kỳ Ruby class nào. Thực tế, đối với Ruby, Class là Module, như chúng ta thấy dưới trong irb console:

> Class.is_a?(Module) => true

Tương tự class, với module chúng ta cũng group methods và constants và share code. Tuy nhiên, có vài khác biệt giữa module và plain Ruby class:

  • Chúng ta bắt đầu definition với keyword module thay vì class;
  • Chúng ta không thể instantiate module, nên không object nào được tạo từ nó;
  • Chúng ta không thể inherit từ module, nên dùng chúng as mixins thay thế;
  • Modules là standalone code, nên không có inheritance hierarchy của modules;

Modules là nơi tuyệt vời để đặt services, concerns, constants và bất kỳ code nào, bằng việc có cùng trách nhiệm nên ở cùng nhau.

Đây là module trông như thế nào:

# lib/modules/invoice_creator.rb module InvoiceCreator TAX_FEE = 0.5 def self.generate puts "Don't worry! I'll generate the invoice for you at #{TAX_FEE}%" end def invoice_total puts "I'll return the invoice total" 1000 end end

Trong ví dụ này, chúng ta quan sát module cung cấp hai loại method: module methods và instance methods.

self.generate là module method, nghĩa là chúng ta dùng được nó mà không cần include (hoặc extend) module vào object nào. Rất common khi tạo service objects. Chúng ta gọi module method như sau:

2.5.3 :006 > InvoiceCreator.generate Don't worry! I'll generate the invoice for you => nil

invoice_total là instance method và để dùng được nó chúng ta cần include module vào class, như sau:

# app/models/invoice.rb class Invoice < ApplicationRecord include InvoiceCreator # This includes our module in the Invoice class def calculate_tax total = invoice_total # included method from our module tax = total * InvoiceCreator::TAX_FEE puts "This is the invoice tax: #{tax}" end end

Tất cả instance methods từ InvoiceCreator trở nên available cho Invoice instances, nên chúng ta gọi calculate_tax dễ dàng:

2.5.3 :008 > Invoice.new.calculate_tax "I'll return the invoice total" "This is the invoice tax: 500.0" => nil 2.5.3 :009 >

Thêm vào đó, bên trong calculate_tax bạn nhận thấy chúng ta dùng constant được định nghĩa trong module. Như tôi nói, modules là great constant keepers!

Hãy tưởng tượng scenario cần hai loại InvoiceCreator generate invoice hoàn toàn khác cho supplier và customer. Chúng ta sẽ end up có name clashing, và để tránh này, dùng benefit lớn kia của modules: namespaces. Hãy xem phần tiếp theo.

Namespaces Mọi Nơi (Namespaces Everywhere)

Namespaces có thể định nghĩa là cách organize code khi muốn tạo local context cho functionality cho trước, chính là thứ chúng ta cần cho scenario vừa mô tả: context khác cho customer's invoice creator và cái khác cho supplier's invoice creator.

Hãy quay lại code. Chúng ta cần tạo hai module khác nhau:

# lib/modules/customer/invoice_creator.rb module Customer module InvoiceCreator def self.generate puts "Don't worry! I'll generate the customer invoice for you" end end end # lib/modules/supplier/invoice_creator.rb module Supplier module InvoiceCreator def self.generate puts "Don't worry! I'll generate the supplier invoice for you" end end end

Sau đó dùng Customer::InvoiceCreator hoặc Supplier::InvoiceCreator nơi cần:

2.5.3 :014 > Customer::InvoiceCreator.generate Don't worry! I'll generate the customer invoice for you => nil 2.5.3 :015 > Supplier::InvoiceCreator.generate Don't worry! I'll generate the supplier invoice for you => nil 2.5.3 :016 >

Cách đó mỗi specific code sẽ wrapped up trong module riêng, respect separation of concerns principle. Bên cạnh đó, namespacing everywhere cũng là cách tuyệt vời keep code well organized.

Hãy xem làm sao dùng benefit kia của Ruby modules: mixins.

Ma thuật của Mixins (The Magic of Using Mixins)

Như bạn có thể biết, một đặc điểm của Ruby là implement single inheritance mechanism, nghĩa là class chỉ inherit từ một class khác. Chúng ta thường cần inherit từ nhiều class. Trong Ruby, chúng ta cover nhu cầu đó bằng composition over inheritance pattern.

Điều này doable bằng mixins. Khi mix in một piece of code vào Ruby class khác, chúng ta thêm behavior cho class này không dùng inheritance và điều này amazing. Do đó, trong Ruby, mixins là modules mà chúng ta include vào class nơi cần. Với đó chúng ta gain bằng cách keep code clean và responsibilities separated, như nên là.

Ví dụ, hãy nói InvoiceCreator module là service cần dùng functionality từ vài module khác, như InvoiceCalculator, InvoiceRenderer và InvoiceSender, chúng necessary để fulfill invoice creation process.

Chúng ta achieve bằng cách include chain of modules as mixins trong code, để dùng method trực tiếp, như ví dụ dưới:

# lib/modules/invoice_calculator.rb module InvoiceCalculator def calculate_items_total puts "imagine some math here" end end # lib/modules/invoice_renderer.rb module InvoiceRenderer def generate_invoice_pdf puts "imagine that we are using some PDF generation magic here" end end # lib/modules/invoice_sender.rb module InvoiceSender def send_invoice puts "imagine your favorite mail service being used here" end end

Vì không thể làm InvoiceCreator inherit từ ba module kia, chúng ta include chúng thay vào đó. Cách này InvoiceCreator include tất cả method từ Invoice module khác, và chúng ta gọi chúng trong bất kỳ class/module nào InvoiceCreator được include. Cẩn thận! Nếu bất kỳ module nào có method trùng tên, chúng sẽ overwrite nhau.

# lib/modules/customer/invoice_creator.rb module Customer module InvoiceCreator include InvoiceCalculator include InvoiceRenderer include InvoiceSender def generate_invoice calculate_items_total # from InvoiceCalculator generate_invoice_pdf # from InvoiceRenderer send_invoice # from InvoiceSender end end end

Bây giờ chúng ta có thể gọi service methods ở đâu include nó bằng cách:

# app/models/invoice.rb class Invoice < ApplicationRecord include Customer::InvoiceCreator def send_invoice_to_customer puts "Don't worry! I'll generate the customer invoice for you" generate_invoice end end

Đây sẽ là kết quả, gọi methods từ included Invoice modules qua InvoiceCreator module:

2.5.3 :051 > Invoice.new.generate_invoice Don't worry! I'll generate the supplier invoice for you imagine some math here imagine that we are using some PDF generation magic here imagine your favorite mail service being used here => nil

Chú ý đây là cách dùng composition over inheritance principle. Principle này nói bạn nên prefer composition khi có thể. Trong composition, chúng ta tạo class responsible cung cấp specific functionality cho others, chính xác điều chúng ta đang làm.

Đó là Tất Cả (That's all Folks!)

Là Ruby developer, chúng ta thích dùng facilities và syntax sugar của Ruby khi planning, writing, maintaining và refactoring code. Tôi hy vọng qua bài này bạn appreciate tại sao modules là great resource giúp cải thiện readability, keep things single responsibility và keep codebase clean, easy maintain. Và cũng dùng mixins magic để include modules trong code khi cần inherit từ multiple sources.

Chúng ta sẽ tiếp tục nói về greatest resources của Ruby và Rails ở đây, stay tuned!

P.S. Nếu bạn muốn đọc bài Ruby Magic ngay khi ra mắt, hãy subscribe newsletter Ruby Magic và không bao giờ bỏ lỡ bài nào!