Come usare le preoccupazioni in Rails 4

Il generatore di progetto Rails 4 predefinito ora crea la directory “preoccupazioni” sotto controller e modelli. Ho trovato alcune spiegazioni su come utilizzare i problemi di instradamento, ma nulla su controller o modelli.

Sono abbastanza sicuro che abbia a che fare con l’attuale “tendenza DCI” nella comunità e vorrei provarlo.

La domanda è: come dovrei usare questa funzione, c’è una convenzione su come definire la gerarchia di nomi / classi per farla funzionare? Come posso includere un problema in un modello o controller?

Così l’ho scoperto da solo. In realtà è un concetto piuttosto semplice ma potente. Ha a che fare con il riutilizzo del codice come nell’esempio qui sotto. Fondamentalmente, l’idea è di estrarre pezzi di codice comuni e / o specifici al contesto per ripulire i modelli ed evitare che diventino troppo grossi e disordinati.

Ad esempio, inserirò uno schema ben noto, il pattern 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 

Quindi, seguendo l’esempio del Prodotto, puoi aggiungere Taggable a qualsiasi class desideri e condividere la sua funzionalità.

Questo è abbastanza ben spiegato da DHH :

In Rails 4, inviteremo i programmatori a utilizzare le preoccupazioni con le app / modelli / problemi di default e le app / controller / preoccupazioni che fanno automaticamente parte del percorso di caricamento. Insieme al wrapper ActiveSupport :: Concern, è sufficiente supporto per rendere brillante questo meccanismo di factoring leggero.

Ho letto di come usare i problemi del modello per i modelli di skin-nize e di asciugare i codici dei modelli. Ecco una spiegazione con esempi:

1) ASCIUGARE i codici dei modelli

Prendi in considerazione un modello di articolo, un modello di evento e un modello di commento. Un articolo o un evento ha molti commenti. Un commento appartiene a Articolo o Evento.

Tradizionalmente, i modelli possono assomigliare a questo:

Modello di commento:

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

Modello di articolo:

 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 

Modello di evento

 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 

Come possiamo notare, c'è una parte significativa di codice comune sia all'evento che all'articolo. Usando le preoccupazioni possiamo estrarre questo codice comune in un modulo separato.

Per questo crea un file commentable.rb in app / models / concerns.

 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 

E ora i tuoi modelli assomigliano a questo:

Modello di commento:

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

Modello di articolo:

 class Article < ActiveRecord::Base include Commentable end 

Modello di evento:

 class Event < ActiveRecord::Base include Commentable end 

2) Modelli grasse skin-nizing.

Prendi in considerazione un modello di evento. Un evento ha molti partecipanti e commenti.

In genere, il modello di evento potrebbe apparire come questo

 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 

Modelli con molte associazioni e che hanno la tendenza ad accumulare sempre più codice e diventare ingestibili. Le preoccupazioni forniscono un modo per skin-nize moduli di grasso rendendoli più modularizzati e facili da capire.

Il modello sopra riportato può essere refactoring usando i problemi come di seguito: Creare un file attendable.rb e commentable.rb in app / models / concerns / event folder

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 

E ora usando Concerns, il tuo modello Event si riduce a

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

* Durante l'utilizzo dei dubbi è consigliabile andare per il raggruppamento basato sul dominio piuttosto che sul raggruppamento "tecnico". Il raggruppamento basato sul dominio è come 'Commentabile', 'Photoable', 'Attendable'. Raggruppamento tecnico significa "ValidationMethods", "FinderMethods" ecc

Vale la pena menzionare che l’uso delle preoccupazioni è considerato una ctriggers idea da molti.

  1. come questo ragazzo
  2. e questo

Alcuni motivi:

  1. C’è un po ‘di magia nera che accade dietro le quinte – La preoccupazione include metodo include , c’è un intero sistema di gestione delle dipendenze – troppa complessità per qualcosa che è banale, buono, vecchio mix di Ruby.
  2. Le tue lezioni non sono meno secche. Se metti 50 metodi pubblici in vari moduli e li includi, la tua class ha ancora 50 metodi pubblici, è solo che nascondi quell’odore di codice, una sorta di mettere la tua spazzatura nei cassetti.
  3. Codebase è in realtà più difficile da navigare con tutte quelle preoccupazioni in giro.
  4. Sei sicuro che tutti i membri del tuo team abbiano la stessa comprensione di ciò che dovrebbe davvero preoccupare?

Le preoccupazioni sono un modo semplice per spararti a una gamba, stai attento con loro.

Questo post mi ha aiutato a capire le preoccupazioni.

 # app/models/trader.rb class Trader include Shared::Schedule end # app/models/concerns/shared/schedule.rb module Shared::Schedule extend ActiveSupport::Concern ... end 

Ho sentito la maggior parte degli esempi qui dimostrando la potenza del module piuttosto che come ActiveSupport::Concern aggiunge valore al module .

Esempio 1: moduli più leggibili.

Quindi, senza preoccupazioni, come sarà un tipico module .

 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 

Dopo aver effettuato il refactoring con 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 

Vedete i metodi di istanza, i metodi di class e il blocco incluso sono meno disordinati. Le preoccupazioni li iniettano in modo appropriato per te. Questo è uno dei vantaggi dell’utilizzo di ActiveSupport::Concern .


Esempio 2: gestire le dipendenze del modulo con garbo.

 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 

In questo esempio, Bar è il modulo di cui Host veramente bisogno. Ma dal momento che Bar ha dipendenza con Foo la class Host deve include Foo (ma aspetta perché Host vuole sapere di Foo ? Può essere evitato?).

Quindi Bar aggiunge dipendenza ovunque vada. E l’ ordine di inclusione conta anche qui. Questo aggiunge molta complessità / dipendenza a una enorme base di codice.

Dopo aver effettuato il refactoring con 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 

Ora sembra semplice.

Se stai pensando perché non possiamo aggiungere la dipendenza Foo nel modulo Bar stesso? Non funzionerà poiché method_injected_by_foo_to_host_klass deve essere iniettato in class, incluso Bar non sul modulo Bar stesso.

Fonte: Rails ActiveSupport :: Concern

Nelle preoccupazioni, rendi il file nomefile.rb

Ad esempio voglio nella mia applicazione dove attributo create_by esiste aggiornare lì valore di 1 e 0 per aggiornamento_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 

dopo che includi nel tuo modello in questo modo:

 class Role < ActiveRecord::Base include TestConcern end