Rails 4'te endişeler nasıl kullanılır?


628

Varsayılan Rails 4 proje üreticisi artık denetleyiciler ve modeller altında "endişeler" dizinini oluşturur. Yönlendirme endişelerinin nasıl kullanılacağı hakkında bazı açıklamalar buldum, ancak kontrolörler veya modeller hakkında hiçbir şey yok.

Toplumdaki mevcut "DCI trendi" ile ilgisi olduğundan eminim ve denemek istiyorum.

Soru şu: Bu özelliği nasıl kullanmam gerekiyor, çalışması için adlandırma / sınıf hiyerarşisinin nasıl tanımlanacağı konusunda bir kural var mı? Bir model veya denetleyiciye nasıl bir endişe ekleyebilirim?

Yanıtlar:


617

Ben de kendim buldum. Aslında oldukça basit ama güçlü bir kavram. Aşağıdaki örnekte olduğu gibi kodun yeniden kullanılmasıyla ilgilidir. Temel olarak, fikir modelleri temizlemek ve çok şişman ve dağınık olmalarını önlemek için ortak ve / veya bağlama özgü kod parçalarını çıkarmaktır.

Örnek olarak, iyi bilinen bir deseni, taranabilir deseni koyacağım:

# 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

Böylece Ürün örneğini takiben, İstediğiniz herhangi bir sınıfa Taggable'ı ekleyebilir ve işlevselliğini paylaşabilirsiniz.

Bu DHH tarafından oldukça iyi açıklanmıştır :

Rails 4'te, programcıları otomatik olarak yükleme yolunun parçası olan varsayılan uygulama / modeller / endişeler ve uygulama / denetleyiciler / endişeler dizinleriyle ilgili endişeleri kullanmaya davet edeceğiz. ActiveSupport :: Concern sarıcı ile birlikte, bu hafif faktoring mekanizmasının parlamasını sağlamak için yeterli destek.


11
DCI bir Bağlam ile ilgilenir, zihinsel bir modeli / kullanım senaryosunu kodlamak için tanımlayıcı olarak Rolleri kullanır ve hiçbir sarmalayıcı kullanılmasını gerektirmez (yöntemler çalışma zamanında doğrudan nesneye bağlanır), bu yüzden DCI ile gerçekten ilgisi yoktur.
ciscoheat

2
@yagooar çalışma zamanında dahil etmek bile DCI yapmazdı. Bir ruby ​​DCI örnek uygulaması görmek istiyorsanız. Ya bir göz atın fulloo.info veya örneklere github.com/runefs/Moby veya Ruby DCI yapmak bordo nasıl kullanılacağı ve ne DCI olduğunu runefs.com (Ne DCI olduğunu. Sonrası ben oldum dizisidir kısa süre önce başladı)
Rune FS

1
@RuneFS && ciscoheat ikiniz de haklısınız. Makaleleri ve gerçekleri tekrar analiz ettim. Geçen hafta sonu, DCI hakkında bir konuşmanın yapıldığı Ruby konferansına gittim ve sonunda felsefesi hakkında biraz daha fazla şey anladım. Metni değiştirdi, böylece DCI'den hiç bahsetmiyor.
yagooar

9
Sınıf yöntemlerinin özel olarak adlandırılmış bir ClassMethods modülünde tanımlanması gerektiği ve bu modülün de temel sınıf tarafından ActiveSupport :: Concern olarak genişletildiğinden bahsetmeye değer (ve muhtemelen bir örnek dahil).
2013

1
Bu örnek için teşekkür ederim, özellikle b / c Aptal oluyordum ve ClassMethods modülünün içindeki Class düzey yöntemlerimi kendim ile tanımladım ve hala işe yaramadı = P
Ryan Crews

379

Cildinizdeki yağ modelleri için model endişelerinin yanı sıra model kodlarınızı KURUYUN hakkında model kaygıları okuyorum . İşte örneklerle bir açıklama:

1) Model kodlarını KURMA

Bir Makale modeli, bir Olay modeli ve Yorum modeli düşünün. Bir makalenin veya etkinliğin birçok yorumu vardır. Bir yorum Makale veya Etkinliğe aittir.

Geleneksel olarak, modeller şöyle görünebilir:

Yorum Modeli:

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

Makale Modeli:

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

Etkinlik Modeli

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

Fark edebileceğimiz gibi, hem Etkinlik hem de Makale için ortak önemli bir kod parçası var. Endişeleri kullanarak, bu ortak kodu Commentable adlı ayrı bir modülde çıkarabiliriz.

Bunun için app / models / endişelerde commentable.rb dosyası oluşturun.

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

Ve şimdi modelleriniz şöyle görünüyor:

Yorum Modeli:

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

Makale Modeli:

class Article < ActiveRecord::Base
  include Commentable
end

Etkinlik Modeli:

class Event < ActiveRecord::Base
  include Commentable
end

2) Cilt Yapıcı Yağ Modelleri.

Bir Etkinlik modeli düşünün. Bir etkinliğin birçok katılımcısı ve yorumu vardır.

Tipik olarak, etkinlik modeli şöyle görünebilir

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

Birçok ilişkilendirmeye sahip ve aksi takdirde daha fazla kod biriktirme ve yönetilememe modelleri vardır. Endişeler, cilt modülleri için daha modüler ve anlaşılması kolay yağ modülleri sağlar.

Yukarıdaki model, aşağıdaki gibi endişeler kullanılarak yeniden düzenlenebilir: Uygulama / modeller / endişeler / etkinlik klasöründe bir attendable.rbve commentable.rbdosya oluşturun

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

Ve şimdi Endişeleri kullanarak Etkinlik modeliniz

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

* Endişeleri kullanırken, 'teknik' gruplama yerine 'alan adı' tabanlı gruplama yapılması önerilir. Etki Alanı Tabanlı gruplandırma 'Yorumlanabilir', 'Fotoğraflanabilir', 'Katlanabilir' gibidir. Teknik gruplandırma 'ValidationMethods', 'FinderMethods' vb.


6
Endişeler miras veya arayüzler mi yoksa çoklu miras kullanmanın bir yolu mu? Ortak bir temel sınıf oluşturma ve bu ortak temel sınıftan alt sınıf oluşturmada sorun nedir?
Chloe

3
Gerçekten @Chloe, ben bazı kırmızı, bir 'endişeler' dizinine sahip bir Rails uygulaması aslında bir 'endişe' ...
Ziyan Junaideen

Tüm yöntemlerinizi tanımlamak için 'dahil' bloğunu kullanabilirsiniz ve şunları içerir: sınıf yöntemleri (ile def self.my_class_method), örnek yöntemleri ve sınıf kapsamındaki yöntem çağrıları ve yönergeleri. Gerek yokmodule ClassMethods
Fader Darkly

1
Endişe duyduğum sorun, modele doğrudan işlevsellik eklemeleridir. add_itemÖrneğin, her iki endişe de uygulanıyorsa , örneğin, mahvoluyorsunuz. Bazı doğrulayıcılar çalışmayı bıraktığında Rails'in kırıldığını, ancak birisinin any?endişe içinde olduğunu düşündüğünü hatırlıyorum . Farklı bir çözüm öneriyorum: Endişeyi farklı bir dilde bir arayüz gibi kullanın. İşlevselliği tanımlamak yerine, bu işlevselliği işleyen ayrı bir sınıf örneğine yapılan başvuruyu tanımlar. Sonra bir şey yapan daha küçük, daha temiz sınıflarınız var ...
A Fader Darkly

@aaditi_jain: Yanlış anlaşılmayı önlemek için lütfen küçük değişiklikleri düzeltin. ie "Uygulama / modeller / endişeler / etkinlik klasöründe bir attendable.rd ve commentable.rb dosyası oluşturun" -> attendable.rd attendable.rb olmalı. Teşekkürler
Rubyist

97

Endişeleri kullanmanın birçok kişi tarafından kötü fikir olarak kabul edildiğini belirtmek gerekir.

  1. bu adam gibi
  2. ve bu

Bazı nedenler:

  1. Perde arkasında karanlık bir sihir oluyor - Endişe yamalı include yöntem, bütün bağımlılık işleme sistemi var - önemsiz iyi eski Ruby mixin deseni olan bir şey için çok fazla karmaşıklık.
  2. Dersleriniz daha az kuru değil. Çeşitli modüllerde 50 genel yöntem doldurursanız ve bunları dahil ederseniz, sınıfınızda hala 50 genel yöntem vardır, bu kod kokusunu gizlersiniz, çöpünüzü çekmecelere koyun.
  3. Kod temeli, etraftaki tüm bu endişelerle gezinmek daha zordur.
  4. Ekibinizin tüm üyelerinin endişe yerine neyin yerini alması gerektiği konusunda aynı anlayışa sahip olduğundan emin misiniz?

Endişeler bacağınızı vurmanın kolay yoludur, onlara dikkat edin.


1
SO'nun bu tartışma için en iyi yer olmadığını biliyorum, ama başka hangi Ruby mixin türü derslerinizi kuru tutar? Daha iyi OO tasarımı, hizmetler katmanı veya eksik olduğum başka bir şey için dava açmadıkça, argümanlarınızdaki # 1 ve # 2'nin nedenleri ters gibi görünüyor? (Katılmıyorum - alternatifler eklemenizi
öneririm

2
Github.com/AndyObtiva/super_module kullanmak bir seçenektir, iyi eski ClassMethods kalıpları kullanmak başka bir seçenektir. Ve endişeleri temiz bir şekilde ayırmak için daha fazla nesne (hizmetler gibi) kullanmak kesinlikle gidilecek yoldur.
Dr.Strangelove

4
Aşağı oylama çünkü bu sorunun cevabı değil. Bu bir görüş. Eminim bir yarar olduğunu düşünüyorum ama StackOverflow bir soruya cevap olmamalıdır.
Adam

2
@Adam Bu fikirli bir cevap. Birisinin raylarda küresel değişkenleri nasıl kullanacağını soracağını düşünün, elbette bir şeyler yapmanın daha iyi yollarının (yani Redis.current vs $ redis) konu başlatıcısı için yararlı bilgi olabileceğini belirtin. Yazılım geliştirme doğası gereği görüşlü bir disiplindir, etrafta bir şey yoktur. Aslında, fikirleri cevaplar ve tartışmalar olarak görüyorum, bu da stackoverflow üzerinde her zaman en iyisidir ve iyi bir şeydir
Dr.Strangelove

2
Elbette, soruya cevabınızla birlikte bahsetmek iyi görünüyor. Cevabınızdaki hiçbir şey aslında OP'nin sorusunu cevaplamıyor. Eğer yapmak istediğiniz tek şey, neden endişe veya küresel değişkenler kullanmamaları gerektiğini uyarmaksa, bu onların sorularına ekleyebileceğiniz iyi bir yorum yapar, ancak gerçekten iyi bir cevap vermez.
Adam


46

Buradaki örneklerin çoğunun modulenasıl ActiveSupport::Concerndeğer kattığı yerine gücünü gösterdiğini hissettim module.

Örnek 1: Daha okunabilir modüller.

Endişesiz bu tipik bir nasıl moduleolacak.

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

İle yeniden düzenleme sonra 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

Örnek yöntemleri, sınıf yöntemleri ve eklenen bloğun daha az dağınık olduğunu görüyorsunuz. Endişeler bunları sizin için uygun şekilde enjekte edecektir. Bu kullanmanın bir avantajı ActiveSupport::Concern.


Örnek 2: Modül bağımlılıklarını dikkatlice ele alın.

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

Bu örnekte gerçekten ihtiyaç duyulan Barmodül var Host. Ama bu yana Barolan bağımlılığı sınıfa sahip (ama neden yaptığını beklemek bilmek istiyorum ? O önlenebilir?).FooHostinclude FooHostFoo

Böylece Bargittiği her yere bağımlılık ekler. Ve içerme sırası da burada önemlidir. Bu, büyük kod tabanına çok fazla karmaşıklık / bağımlılık ekler.

Yeniden düzenleme işleminden sonra 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

Şimdi basit görünüyor.

Düşünüyorsanız neden modülün kendisine Foobağımlılık ekleyemeyiz Bar? Modülün kendisine method_injected_by_foo_to_host_klassdahil Barolmayan bir sınıfa enjekte edilmesi gerektiğinden bu işe yaramaz Bar.

Kaynak: Rails ActiveSupport :: Endişe


bunun için teşekkürler. Avantajlarının ne olduğunu merak etmeye başladım ...
Hari Karam Singh

FWIW bu dokümanlardan kabaca kopyalayıp yapıştırabilirsiniz .
Dave Newton

7

Endişe duyduğunuzda dosya dosyası.rb yapın

Örneğin, create_by özniteliğinin mevcut olduğu uygulamamda, update_by için 1 ve 0 değerini güncelleyin.

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

Eğer argümanları eylemde geçirmek istiyorsanız

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

Bundan sonra modelinize şu şekilde ekleyin:

class Role < ActiveRecord::Base
  include TestConcern
end
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.