Cara menggunakan kekhawatiran di Rails 4


628

Generator proyek Rails 4 default sekarang menciptakan direktori "keprihatinan" di bawah pengontrol dan model. Saya telah menemukan beberapa penjelasan tentang cara menggunakan masalah perutean, tetapi tidak ada tentang pengontrol atau model.

Saya cukup yakin ini ada hubungannya dengan "tren DCI" saat ini di komunitas dan ingin mencobanya.

Pertanyaannya adalah, bagaimana saya seharusnya menggunakan fitur ini, apakah ada konvensi tentang cara mendefinisikan hirarki penamaan / kelas untuk membuatnya berfungsi? Bagaimana saya bisa memasukkan masalah dalam model atau pengontrol?

Jawaban:


617

Jadi saya menemukannya sendiri. Ini sebenarnya konsep yang cukup sederhana namun kuat. Ini berkaitan dengan penggunaan kembali kode seperti pada contoh di bawah ini. Pada dasarnya, idenya adalah untuk mengekstrak potongan kode umum dan / atau konteks tertentu untuk membersihkan model dan menghindari mereka menjadi terlalu gemuk dan berantakan.

Sebagai contoh, saya akan meletakkan satu pola terkenal, pola taggable:

# 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

Jadi mengikuti sampel Produk, Anda dapat menambahkan Taggable ke kelas yang Anda inginkan dan membagikan fungsinya.

Ini dijelaskan dengan cukup baik oleh DHH :

Di Rails 4, kita akan mengundang pemrogram untuk menggunakan keprihatinan dengan direktori / model / kekhawatiran default dan direktori app / controllers / concern yang secara otomatis menjadi bagian dari jalur pemuatan. Bersama dengan ActiveSupport :: Wrapper Concern, hanya cukup dukungan untuk membuat mekanisme anjak ringan ini bersinar.


11
DCI berurusan dengan Konteks, menggunakan Peran sebagai pengidentifikasi untuk memetakan model mental / use case ke kode, dan tidak memerlukan pembungkus untuk digunakan (metode terikat langsung ke objek saat runtime) jadi ini tidak ada hubungannya dengan DCI benar-benar.
ciscoheat

2
@yagooar bahkan termasuk saat runtime tidak akan berhasil menjadi DCI. Jika Anda ingin melihat implementasi contoh DCI ruby. Lihatlah baik fulloo.info atau contoh di github.com/runefs/Moby atau untuk bagaimana menggunakan maroon untuk melakukan DCI di Ruby dan apa DCI itu runefs.com (Apa DCI itu. Adalah serangkaian posting yang saya punya baru saja mulai)
Rune FS

1
@RuneFS && ciscoheat kalian berdua benar. Saya hanya menganalisis artikel dan fakta lagi. Dan, saya pergi akhir pekan lalu ke sebuah konferensi Ruby di mana satu pembicaraan tentang DCI dan akhirnya saya mengerti sedikit tentang filosofinya. Mengubah teks sehingga tidak menyebutkan DCI sama sekali.
yagooar

9
Perlu disebutkan (dan mungkin termasuk dalam contoh) bahwa metode kelas seharusnya didefinisikan dalam modul ClassMethods bernama khusus, dan bahwa modul ini diperluas oleh kelas dasar menjadi ActiveSupport :: Kepedulian juga.
febeling

1
Terima kasih atas contoh ini, terutama b / c Saya menjadi bodoh dan mendefinisikan metode level Kelas saya di dalam modul ClassMethods dengan self.whever masih, dan itu tidak bekerja = P
Ryan Crews

379

Saya telah membaca tentang menggunakan kekhawatiran model untuk model lemak menguliti serta KERING kode kode Anda. Berikut ini penjelasan dengan contoh:

1) MENGERINGKAN kode model

Pertimbangkan model Artikel, model Acara, dan model Komentar. Artikel atau acara memiliki banyak komentar. Komentar adalah milik Artikel atau Acara.

Secara tradisional, modelnya mungkin terlihat seperti ini:

Model komentar:

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

Model artikel:

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

Model Peristiwa

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

Seperti yang bisa kita perhatikan, ada kode penting yang sama untuk Event dan Artikel. Dengan menggunakan kekhawatiran, kami dapat mengekstrak kode umum ini dalam modul terpisah yang dapat dikomentari.

Untuk ini, buat file commentable.rb di app / models / concern.

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

Dan sekarang model Anda terlihat seperti ini:

Model komentar:

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

Model artikel:

class Article < ActiveRecord::Base
  include Commentable
end

Model Peristiwa:

class Event < ActiveRecord::Base
  include Commentable
end

2) Model Lemak yang Menguliti Kulit.

Pertimbangkan model Peristiwa. Sebuah acara memiliki banyak pengunjung dan komentar.

Biasanya, model acara mungkin terlihat seperti ini

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

Model dengan banyak asosiasi dan sebaliknya memiliki kecenderungan untuk mengakumulasi semakin banyak kode dan menjadi tidak terkelola. Kekhawatiran memberikan cara untuk menguliti modul lemak sehingga modul tersebut lebih termodulasi dan mudah dipahami.

Model di atas dapat di-refactored dengan menggunakan kekhawatiran seperti di bawah ini: Membuat attendable.rbdan commentable.rbfile di folder app / models / concern / event

hadir.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

commentable.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

Dan sekarang menggunakan Kekhawatiran, model Acara Anda berkurang menjadi

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

* Saat menggunakan masalah, disarankan untuk menggunakan pengelompokan berbasis 'domain' daripada pengelompokan 'teknis'. Pengelompokan Berbasis Domain seperti 'Commentable', 'Photoable', 'Attendable'. Pengelompokan teknis akan berarti 'ValidationMethods', 'FinderMethods' dll


6
Jadi Kekhawatiran hanyalah cara untuk menggunakan pewarisan atau antarmuka atau pewarisan berganda? Apa yang salah dengan membuat kelas dasar umum dan subkelas dari kelas dasar umum itu?
Chloe

3
Memang @ Chloe, saya di mana merah, aplikasi Rails dengan direktori 'kekhawatiran' sebenarnya adalah 'keprihatinan' ...
Ziyan Junaideen

Anda dapat menggunakan blok 'termasuk' untuk mendefinisikan semua metode Anda dan termasuk: metode kelas (dengan def self.my_class_method), metode contoh dan pemanggilan metode dan arahan dalam ruang lingkup kelas. Tidak perlu untukmodule ClassMethods
A Fader Darkly

1
Masalah yang saya miliki dengan keprihatinan adalah mereka menambahkan fungsionalitas langsung ke model. Jadi, jika dua masalah sama-sama diterapkan add_item, misalnya, Anda kacau. Saya ingat berpikir Rails rusak ketika beberapa validator berhenti bekerja, tetapi seseorang telah mengimplementasikannya any?dalam masalah. Saya mengusulkan solusi yang berbeda: gunakan kekhawatiran seperti antarmuka dalam bahasa yang berbeda. Alih-alih mendefinisikan fungsionalitas, ia mendefinisikan referensi ke instance kelas terpisah yang menangani fungsionalitas itu. Maka Anda memiliki kelas yang lebih kecil dan lebih rapi yang melakukan satu hal ...
A Fader Darkly

@aaditi_jain: Harap perbaiki perubahan kecil untuk menghindari kesalahpahaman. yaitu "Buat attendable.rd dan file yang commentable.rb di app / model / keprihatinan / folder acara" -> attendable.rd harus attendable.rb Thanks
Rubyist

97

Perlu disebutkan bahwa menggunakan kekhawatiran dianggap sebagai gagasan buruk oleh banyak orang.

  1. seperti orang ini
  2. dan yang satu ini

Beberapa alasan:

  1. Ada beberapa sihir gelap yang terjadi di belakang layar - Kepedulian adalah includemetode penambalan , ada sistem penanganan ketergantungan secara keseluruhan - terlalu banyak kerumitan untuk sesuatu yang merupakan pola Ruby mixin tua yang baik.
  2. Kelas Anda tidak kurang kering. Jika Anda memasukkan 50 metode umum dalam berbagai modul dan memasukkannya, kelas Anda masih memiliki 50 metode publik, hanya saja Anda menyembunyikan bau kode itu, semacam menaruh sampah Anda di dalam laci.
  3. Basis kode sebenarnya lebih sulit untuk dinavigasi dengan semua kekhawatiran di sekitar.
  4. Apakah Anda yakin semua anggota tim Anda memiliki pemahaman yang sama tentang apa yang seharusnya menjadi perhatian?

Kekhawatiran adalah cara mudah untuk menembak diri sendiri di kaki, hati-hati dengan mereka.


1
Saya tahu SO bukan tempat terbaik untuk diskusi ini, tetapi apa jenis Ruby mixin lainnya yang membuat kelas Anda kering? Sepertinya alasan # 1 dan # 2 dalam argumen Anda kontra, kecuali Anda hanya membuat kasus untuk desain OO yang lebih baik, lapisan layanan, atau sesuatu yang saya hilang? (Saya tidak setuju - saya sarankan menambahkan alternatif membantu!)
toobulkeh

2
Menggunakan github.com/AndyObtiva/super_module adalah salah satu opsi, menggunakan pola ClassMethods lama yang baik adalah pilihan lain. Dan menggunakan lebih banyak objek (seperti layanan) untuk memisahkan kekhawatiran jelas merupakan cara untuk pergi.
Dr.Strangelove

4
Menunduk karena ini bukan jawaban untuk pertanyaan. Itu pendapat. Itu pendapat yang saya yakin memiliki kelebihannya tetapi itu seharusnya tidak menjadi jawaban atas pertanyaan di StackOverflow.
Adam

2
@ Adam Ini adalah jawaban yang disarankan. Bayangkan seseorang akan bertanya bagaimana menggunakan variabel global dalam rel, tentu menyebutkan bahwa ada cara yang lebih baik untuk melakukan sesuatu (yaitu Redis.current vs $ redis) dapat menjadi info yang berguna untuk topik starter? Pengembangan perangkat lunak secara inheren merupakan disiplin ilmu, tidak ada jalan lain. Bahkan, saya melihat pendapat sebagai jawaban dan diskusi yang merupakan jawaban terbaik sepanjang waktu di stackoverflow, dan itu adalah hal yang baik
Dr.Strangelove

2
Tentu, menyebutkannya bersama dengan jawaban Anda untuk pertanyaan itu tampaknya baik-baik saja. Tidak ada dalam jawaban Anda yang benar-benar menjawab pertanyaan OP. Jika semua yang ingin Anda lakukan adalah memperingatkan seseorang mengapa mereka tidak boleh menggunakan kekhawatiran atau variabel global maka itu akan membuat komentar yang baik yang dapat Anda tambahkan ke pertanyaan mereka, tetapi itu tidak benar-benar membuat jawaban yang baik.
Adam


46

Saya merasa sebagian besar contoh di sini menunjukkan kekuatan moduledaripada bagaimana ActiveSupport::Concernmenambah nilai module.

Contoh 1: Modul yang lebih mudah dibaca.

Jadi tanpa khawatir ini bagaimana tipikal moduleakan.

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

Setelah refactoring dengan 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

Anda melihat metode instan, metode kelas dan blok yang disertakan kurang berantakan. Kekhawatiran akan menyuntikkannya secara tepat untuk Anda. Itu salah satu keuntungan menggunakan ActiveSupport::Concern.


Contoh 2: Tangani dependensi modul dengan anggun.

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

Dalam contoh ini Baradalah modul yang Hostsangat dibutuhkan. Tapi karena Barmemiliki ketergantungan dengan Fooyang Hostkelas harus include Foo(tapi tunggu mengapa Hostingin tahu tentang Foo? Apakah bisa dihindari?).

Jadi Barmenambah ketergantungan di mana pun ia pergi. Dan urutan inklusi juga penting di sini. Ini menambah banyak kompleksitas / ketergantungan pada basis kode besar.

Setelah refactoring dengan 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

Sekarang terlihat sederhana.

Jika Anda berpikir mengapa kita tidak dapat menambahkan Fooketergantungan pada Barmodul itu sendiri? Itu tidak akan berhasil karena method_injected_by_foo_to_host_klassharus disuntikkan di kelas yang termasuk Bartidak pada Barmodul itu sendiri.

Sumber: Rails ActiveSupport :: Concern


Terima kasih untuk itu. Saya mulai bertanya-tanya apa keuntungan mereka ...
Hari Karam Singh

FWIW ini kira-kira copy-paste dari dokumen .
Dave Newton

7

Dalam keprihatinan membuat file filename.rb

Sebagai contoh saya ingin di aplikasi saya di mana atribut create_by ada pembaruan nilai ada 1, dan 0 untuk updated_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

Jika Anda ingin memberikan argumen dalam tindakan

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

setelah itu sertakan dalam model Anda seperti ini:

class Role < ActiveRecord::Base
  include TestConcern
end
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.