Cách sử dụng mối quan tâm trong Rails 4


628

Trình tạo dự án Rails 4 mặc định hiện tạo thư mục "quan tâm" trong bộ điều khiển và mô hình. Tôi đã tìm thấy một số giải thích về cách sử dụng mối quan tâm định tuyến, nhưng không có gì về bộ điều khiển hoặc mô hình.

Tôi khá chắc chắn rằng nó có liên quan đến "xu hướng DCI" hiện tại trong cộng đồng và muốn thử.

Câu hỏi là, làm thế nào tôi có thể sử dụng tính năng này, có một quy ước về cách xác định hệ thống phân cấp đặt tên / lớp để làm cho nó hoạt động không? Làm thế nào tôi có thể bao gồm một mối quan tâm trong một mô hình hoặc bộ điều khiển?

Câu trả lời:


617

Vì vậy, tôi tìm thấy nó một mình. Nó thực sự là một khái niệm khá đơn giản nhưng mạnh mẽ. Nó phải làm với việc tái sử dụng mã như trong ví dụ dưới đây. Về cơ bản, ý tưởng là trích xuất các đoạn mã cụ thể và / hoặc bối cảnh cụ thể để làm sạch các mô hình và tránh chúng trở nên quá béo và lộn xộn.

Ví dụ: tôi sẽ đặt một mẫu nổi tiếng, mẫu có thể gắn thẻ:

# app/models/product.rb
class Product
  include Taggable

  ...
end

# app/models/concerns/taggable.rb
# notice that the file name has to match the module name 
# (applying Rails conventions for autoloading)
module Taggable
  extend ActiveSupport::Concern

  included do
    has_many :taggings, as: :taggable
    has_many :tags, through: :taggings

    class_attribute :tag_limit
  end

  def tags_string
    tags.map(&:name).join(', ')
  end

  def tags_string=(tag_string)
    tag_names = tag_string.to_s.split(', ')

    tag_names.each do |tag_name|
      tags.build(name: tag_name)
    end
  end

  # methods defined here are going to extend the class, not the instance of it
  module ClassMethods

    def tag_limit(value)
      self.tag_limit_value = value
    end

  end

end

Vì vậy, theo mẫu Sản phẩm, bạn có thể thêm Taggable vào bất kỳ lớp nào bạn muốn và chia sẻ chức năng của nó.

Điều này được giải thích khá tốt bởi DHH :

Trong Rails 4, chúng tôi sẽ mời các lập trình viên sử dụng các mối quan tâm với các thư mục ứng dụng / mô hình / mối quan tâm mặc định và các thư mục ứng dụng / bộ điều khiển / mối quan tâm tự động là một phần của đường dẫn tải. Cùng với trình bao bọc ActiveSupport :: Concern, nó chỉ đủ hỗ trợ để làm cho cơ chế bao thanh toán trọng lượng nhẹ này tỏa sáng.


11
DCI xử lý một Ngữ cảnh, sử dụng Vai trò làm định danh để ánh xạ mô hình tinh thần / trường hợp sử dụng thành mã và không yêu cầu sử dụng trình bao bọc (các phương thức được liên kết trực tiếp với đối tượng trong thời gian chạy) vì vậy điều này thực sự không liên quan gì đến DCI.
ciscoheat

2
@yagooar thậm chí bao gồm nó trong thời gian chạy sẽ không biến nó thành DCI. Nếu bạn muốn xem triển khai ví dụ DCI ruby. Hãy xem fulloo.info hoặc các ví dụ tại github.com/runefs/Moby hoặc để biết cách sử dụng maroon để làm DCI trong Ruby và DCI là gì runefs.com (DCI là gì. Đây là một loạt bài tôi đã chỉ mới bắt đầu gần đây)
Rune FS

1
@RuneFS && ciscoheat bạn đều đúng. Tôi chỉ phân tích các bài báo và sự kiện một lần nữa. Và, cuối tuần trước tôi đã đi đến một hội nghị về Ruby, nơi một cuộc nói chuyện về DCI và cuối cùng tôi đã hiểu thêm một chút về triết lý của nó. Đã thay đổi văn bản để nó không đề cập đến DCI.
yagooar

9
Điều đáng nói (và có lẽ bao gồm trong một ví dụ) rằng các phương thức lớp được cho là được định nghĩa trong một mô-đun ClassMethods có tên đặc biệt và mô-đun này được mở rộng bởi lớp cơ sở là ActiveSupport :: Concern, quá.
febeling

1
Cảm ơn bạn vì ví dụ này, chủ yếu là vì tôi đã bị câm và xác định các phương thức cấp Lớp của mình bên trong mô-đun ClassMethods với self.whthing vẫn còn, và điều đó không hoạt động = P
Ryan Crews

379

Tôi đã đọc về việc sử dụng các mối quan tâm của người mẫu đối với người mẫu béo da cũng như DRY lên mã mô hình của bạn. Dưới đây là một lời giải thích với các ví dụ:

1) DRYing lên mã mô hình

Xem xét mô hình Bài viết, mô hình Sự kiện và mô hình Nhận xét. Một bài viết hoặc một sự kiện có nhiều ý kiến. Một bình luận thuộc về Điều hoặc Sự kiện.

Theo truyền thống, các mô hình có thể trông như thế này:

Mô hình bình luận:

class Comment < ActiveRecord::Base
  belongs_to :commentable, polymorphic: true
end

Mô hình bài viết:

class Article < ActiveRecord::Base
  has_many :comments, as: :commentable 

  def find_first_comment
    comments.first(created_at DESC)
  end

  def self.least_commented
   #return the article with least number of comments
  end
end

Mô hình sự kiện

class Event < ActiveRecord::Base
  has_many :comments, as: :commentable 

  def find_first_comment
    comments.first(created_at DESC)
  end

  def self.least_commented
   #returns the event with least number of comments
  end
end

Như chúng ta có thể nhận thấy, có một đoạn mã đáng kể phổ biến cho cả Sự kiện và Điều. Sử dụng mối quan tâm, chúng tôi có thể trích xuất mã phổ biến này trong một mô-đun riêng biệt Có thể nhận xét.

Đối với điều này, hãy tạo một tệp commentable.rb trong ứng dụng / mô hình / mối quan tâm.

module Commentable
  extend ActiveSupport::Concern

  included do
    has_many :comments, as: :commentable
  end

  # for the given article/event returns the first comment
  def find_first_comment
    comments.first(created_at DESC)
  end

  module ClassMethods
    def least_commented
      #returns the article/event which has the least number of comments
    end
  end
end

Và bây giờ mô hình của bạn trông như thế này:

Mô hình bình luận:

class Comment < ActiveRecord::Base
  belongs_to :commentable, polymorphic: true
end

Mô hình bài viết:

class Article < ActiveRecord::Base
  include Commentable
end

Mô hình sự kiện:

class Event < ActiveRecord::Base
  include Commentable
end

2) Mô hình mỡ nướu da.

Hãy xem xét một mô hình Sự kiện. Một sự kiện có nhiều người tham dự và bình luận.

Thông thường, mô hình sự kiện có thể trông như thế này

class Event < ActiveRecord::Base   
  has_many :comments
  has_many :attenders


  def find_first_comment
    # for the given article/event returns the first comment
  end

  def find_comments_with_word(word)
    # for the given event returns an array of comments which contain the given word
  end 

  def self.least_commented
    # finds the event which has the least number of comments
  end

  def self.most_attended
    # returns the event with most number of attendes
  end

  def has_attendee(attendee_id)
    # returns true if the event has the mentioned attendee
  end
end

Các mô hình với nhiều hiệp hội và mặt khác có xu hướng tích lũy ngày càng nhiều mã và trở nên không thể quản lý được. Mối quan tâm cung cấp một cách để mô-đun chất béo kích thước da làm cho chúng mô-đun hóa nhiều hơn và dễ hiểu.

Mô hình trên có thể được cấu trúc lại bằng các mối quan tâm như dưới đây: Tạo một tệp attendable.rbcommentable.rbtệp trong thư mục ứng dụng / mô hình / mối quan tâm / sự kiện

có thể tham dự.rb

module Attendable
  extend ActiveSupport::Concern

  included do 
    has_many :attenders
  end

  def has_attender(attender_id)
    # returns true if the event has the mentioned attendee
  end

  module ClassMethods
    def most_attended
      # returns the event with most number of attendes
    end
  end
end

bình luận.rb

module Commentable
  extend ActiveSupport::Concern

  included do 
    has_many :comments
  end

  def find_first_comment
    # for the given article/event returns the first comment
  end

  def find_comments_with_word(word)
    # for the given event returns an array of comments which contain the given word
  end

  module ClassMethods
    def least_commented
      # finds the event which has the least number of comments
    end
  end
end

Và hiện đang sử dụng Mối quan tâm, mô hình Sự kiện của bạn giảm xuống còn

class Event < ActiveRecord::Base
  include Commentable
  include Attendable
end

* Trong khi sử dụng mối quan tâm, nên đi theo nhóm dựa trên 'tên miền' thay vì nhóm 'kỹ thuật'. Nhóm dựa trên tên miền giống như 'Có thể nhận xét', 'Có thể chụp ảnh', 'Có thể tham dự'. Nhóm kỹ thuật sẽ có nghĩa là 'Xác thựcMethods', 'FinderMethods', v.v.


6
Vì vậy, mối quan tâm chỉ là một cách để sử dụng kế thừa hoặc giao diện hoặc nhiều kế thừa? Có gì sai khi tạo một lớp cơ sở chung và phân lớp từ lớp cơ sở chung đó?
Chloe

3
Thật vậy, @Chloe, tôi có màu đỏ, một ứng dụng Rails có thư mục 'mối quan tâm' thực sự là một 'mối quan tâm' ...
Ziyan Junaideen

Bạn có thể sử dụng khối 'bao gồm' để xác định tất cả các phương thức của mình và bao gồm: các phương thức lớp (với def self.my_class_method), các phương thức cá thể và các lệnh gọi và chỉ thị phương thức trong phạm vi lớp. Không cầnmodule ClassMethods
Một Fader Darkly

1
Vấn đề tôi gặp phải lo ngại là họ thêm chức năng trực tiếp vào mô hình. Vì vậy, nếu hai mối quan tâm cả hai thực hiện add_item, ví dụ, bạn bị lừa. Tôi nhớ rằng Rails đã bị hỏng khi một số trình xác nhận ngừng hoạt động, nhưng ai đó đã thực hiện any?trong một mối quan tâm. Tôi đề xuất một giải pháp khác: sử dụng mối quan tâm như giao diện bằng một ngôn ngữ khác. Thay vì định nghĩa chức năng, nó định nghĩa tham chiếu đến một thể hiện lớp riêng biệt xử lý chức năng đó. Sau đó, bạn có các lớp nhỏ hơn, gọn gàng hơn làm một việc ...
Một Fader Darkly

@aaditi_jain: Vui lòng sửa thay đổi nhỏ để tránh quan niệm sai lầm. tức là "Tạo tệp có thể tham dự.rd và commentable.rb trong thư mục ứng dụng / mô hình / mối quan tâm / sự kiện" -> có thể tham dự.rd phải có thể tham dự.rb Cảm ơn
Rubyist

97

Điều đáng nói là việc sử dụng mối quan tâm bị nhiều người coi là ý tưởng tồi.

  1. thích anh chàng này
  2. và cái này nữa

Một số lý do:

  1. Có một số phép thuật đen tối xảy ra đằng sau hậu trường - Mối quan tâm là includephương pháp vá , có một hệ thống xử lý phụ thuộc hoàn toàn - quá phức tạp đối với một thứ gì đó là mô hình hỗn hợp Ruby cũ tốt tầm thường.
  2. Lớp học của bạn không kém phần khô khan. Nếu bạn nhét 50 phương thức công khai vào các mô-đun khác nhau và bao gồm chúng, lớp của bạn vẫn có 50 phương thức công khai, chỉ là bạn ẩn mùi mã đó, sắp xếp rác của bạn vào ngăn kéo.
  3. Codebase thực sự khó điều hướng hơn với tất cả những mối quan tâm xung quanh.
  4. Bạn có chắc chắn rằng tất cả các thành viên trong nhóm của bạn có cùng sự hiểu biết về những gì thực sự nên thay thế mối quan tâm?

Mối quan tâm là cách dễ dàng để bắn vào chân bạn, hãy cẩn thận với chúng.


1
Tôi biết SO không phải là nơi tốt nhất cho cuộc thảo luận này, nhưng loại hỗn hợp Ruby nào khác giữ cho lớp học của bạn khô ráo? Có vẻ như lý do số 1 và số 2 trong các đối số của bạn là đối trọng, trừ khi bạn chỉ tạo ra trường hợp để thiết kế OO tốt hơn, lớp dịch vụ hoặc thứ gì khác mà tôi đang thiếu? (Tôi không đồng ý - Tôi khuyên bạn nên thêm các lựa chọn thay thế!)
toobulkeh

2
Sử dụng github.com/AndyObtiva/super_module là một tùy chọn, sử dụng các mẫu ClassMethods cũ là một lựa chọn khác. Và sử dụng nhiều đối tượng (như dịch vụ) để tách biệt mối quan tâm chắc chắn là cách tốt nhất.
Dr.Strangelove

4
Downvote vì đây không phải là một câu trả lời cho câu hỏi. Đó là một ý kiến. Đó là một ý kiến ​​mà tôi chắc chắn rằng nó có giá trị nhưng nó không phải là một câu trả lời cho một câu hỏi trên StackOverflow.
Adam

2
@Adam Đó là một câu trả lời đầy quan điểm. Hãy tưởng tượng ai đó sẽ hỏi làm thế nào để sử dụng các biến toàn cục trong đường ray, chắc chắn đề cập rằng có nhiều cách tốt hơn để làm mọi thứ (ví dụ Redis.c hiện tại so với $ redis) có thể là thông tin hữu ích cho người bắt đầu chủ đề? Phát triển phần mềm vốn dĩ là một ngành học có ý kiến, không có xung quanh nó. Trên thực tế, tôi thấy ý kiến ​​là câu trả lời và thảo luận câu trả lời nào là tốt nhất mọi lúc trên stackoverflow, và đó là một điều tốt
Dr.Strangelove

2
Chắc chắn, đề cập đến nó cùng với câu trả lời của bạn cho câu hỏi có vẻ tốt. Không có gì trong câu trả lời của bạn thực sự trả lời câu hỏi của OP. Nếu tất cả những gì bạn muốn làm là cảnh báo cho ai đó lý do tại sao họ không nên sử dụng mối quan tâm hoặc biến toàn cục thì điều đó sẽ tạo ra một nhận xét tốt mà bạn có thể thêm vào câu hỏi của họ, nhưng nó không thực sự tạo ra câu trả lời hay.
Adam


46

Tôi cảm thấy hầu hết các ví dụ ở đây đã chứng minh sức mạnh của modulenó hơn là cách ActiveSupport::Concerntăng giá trị module.

Ví dụ 1: Các mô-đun dễ đọc hơn.

Vì vậy, không có mối quan tâm này làm thế nào một điển hình modulesẽ được.

module M
  def self.included(base)
    base.extend ClassMethods
    base.class_eval do
      scope :disabled, -> { where(disabled: true) }
    end
  end

  def instance_method
    ...
  end

  module ClassMethods
    ...
  end
end

Sau khi tái cấu trúc với ActiveSupport::Concern.

require 'active_support/concern'

module M
  extend ActiveSupport::Concern

  included do
    scope :disabled, -> { where(disabled: true) }
  end

  class_methods do
    ...
  end

  def instance_method
    ...
  end
end

Bạn thấy các phương thức cá thể, các phương thức lớp và khối bao gồm ít lộn xộn hơn. Mối quan tâm sẽ tiêm chúng thích hợp cho bạn. Đó là một lợi thế của việc sử dụng ActiveSupport::Concern.


Ví dụ 2: Xử lý các phụ thuộc mô đun một cách duyên dáng.

module Foo
  def self.included(base)
    base.class_eval do
      def self.method_injected_by_foo_to_host_klass
        ...
      end
    end
  end
end

module Bar
  def self.included(base)
    base.method_injected_by_foo_to_host_klass
  end
end

class Host
  include Foo # We need to include this dependency for Bar
  include Bar # Bar is the module that Host really needs
end

Trong ví dụ Barnày là mô-đun Hostthực sự cần. Nhưng kể từ khi Barcó sự phụ thuộc với Foocác Hostlớp phải include Foo(nhưng chờ tại sao Hostmuốn biết về Foo? Nó có thể tránh được?).

Vì vậy, Barthêm phụ thuộc ở mọi nơi nó đi. Và thứ tự bao gồm cũng có vấn đề ở đây. Điều này thêm rất nhiều sự phức tạp / phụ thuộc vào cơ sở mã lớn.

Sau khi tái cấu trúc với ActiveSupport::Concern

require 'active_support/concern'

module Foo
  extend ActiveSupport::Concern
  included do
    def self.method_injected_by_foo_to_host_klass
      ...
    end
  end
end

module Bar
  extend ActiveSupport::Concern
  include Foo

  included do
    self.method_injected_by_foo_to_host_klass
  end
end

class Host
  include Bar # It works, now Bar takes care of its dependencies
end

Bây giờ nó trông đơn giản.

Nếu bạn đang nghĩ tại sao chúng ta không thể thêm Foophụ thuộc vào Barchính mô-đun? Điều đó sẽ không hoạt động vì method_injected_by_foo_to_host_klassphải được tiêm vào một lớp bao gồm cả Barkhông phải trên Barchính mô-đun.

Nguồn: Rails ActiveSupport :: Mối quan tâm


cảm ơn vì điều đó. Tôi bắt đầu tự hỏi lợi thế của họ là gì ...
Hari Karam Singh

FWIW đây là bản sao chép-dán từ các tài liệu .
Dave Newton

7

Trong mối quan tâm làm cho tập tin filename.rb

Ví dụ tôi muốn trong ứng dụng của mình, nơi thuộc tính created_by tồn tại cập nhật có giá trị bằng 1 và 0 cho update_by

module TestConcern 
  extend ActiveSupport::Concern

  def checkattributes   
    if self.has_attribute?(:created_by)
      self.update_attributes(created_by: 1)
    end
    if self.has_attribute?(:updated_by)
      self.update_attributes(updated_by: 0)
    end
  end

end

Nếu bạn muốn truyền đối số trong hành động

included do
   before_action only: [:create] do
     blaablaa(options)
   end
end

sau đó bao gồm trong mô hình của bạn như thế này:

class Role < ActiveRecord::Base
  include TestConcern
end
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.