Как использовать проблемы в Rails 4

567

По умолчанию генератор проекта Rails 4 создает каталог "проблемы" под контроллерами и моделями. Я нашел несколько объяснений о том, как использовать проблемы маршрутизации, но ничего о контроллерах или моделях.

Я уверен, что это связано с текущей тенденцией DCI в сообществе и хотел бы попробовать.

Вопрос в том, как я должен использовать эту функцию, существует ли соглашение о том, как определить иерархию именования/класса, чтобы заставить его работать? Как включить проблему в модель или контроллер?

Теги:
ruby-on-rails-4
dci

6 ответов

622
Лучший ответ

Итак, я нашел это сам. На самом деле это довольно простая, но мощная концепция. Это связано с повторным использованием кода, как в приведенном ниже примере. В принципе, идея состоит в том, чтобы извлечь общие и/или контекстно-специфические фрагменты кода, чтобы очистить модели и избежать их слишком толстых и грязных.

В качестве примера, я поставлю один хорошо известный шаблон, taggable pattern:

# 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

Итак, следуя образ продукта, вы можете добавить Taggable в любой класс, который вы хотите, и поделиться своими возможностями.

Это довольно хорошо объясняется DHH:

В Rails 4 собирались пригласить программистов использовать проблемы с приложения по умолчанию/модели/проблемы по умолчанию и приложения/контроллеры/каталоги проблем которые автоматически входят в путь загрузки. Вместе с ActiveSupport:: Контейнер-обертка, его достаточная поддержка, чтобы сделать это светлый механизм факторингового механизма.

  • 11
    DCI имеет дело с контекстом, использует роли в качестве идентификаторов для сопоставления ментальной модели / варианта использования с кодом и не требует использования оболочек (методы привязываются непосредственно к объекту во время выполнения), так что это на самом деле не имеет ничего общего с DCI.
  • 0
    @ciscoheat достаточно было бы включить модуль Taggable не в класс, а во время выполнения в заданном контексте. Но вы правы, этот пример больше фокусируется на повторном использовании кода, чем на DCI.
Показать ещё 11 комментариев
381

Я читал об использовании проблем с моделью для моделирования жировых моделей кожи, а также для СУШКИ ваших кодов модели. Вот объяснение с примерами:

1) Сушка кодов моделей

Рассмотрим модель статьи, модель события и модель комментария. В статье или событии есть много комментариев. Комментарий принадлежит либо к статье, либо к событию.

Традиционно модели могут выглядеть так:

Модель комментария:

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

Модель статьи:

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

Модель события

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

Как мы можем заметить, существует значительный фрагмент кода, общий для обоих событий и статьи. Используя проблемы, мы можем извлечь этот общий код в отдельный модуль Commentable.

Для этого создайте файл commentable.rb в app/models/problems.

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

И теперь ваши модели выглядят так:

Модель комментария:

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

Модель статьи:

class Article < ActiveRecord::Base
  include Commentable
end

Модель события:

class Event < ActiveRecord::Base
  include Commentable
end

2) Моделирование жировых отложений.

Рассмотрим модель события. В мероприятии много посетителей и комментариев.

Как правило, модель события может выглядеть так:

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

Модели со многими ассоциациями и в противном случае имеют тенденцию накапливать все больше и больше кода и становятся неуправляемыми. Озабоченность представляет собой способ сглаживания жировых модулей, что делает их более модульными и понятными.

Вышеупомянутая модель может быть реорганизована с использованием проблем, как показано ниже: Создайте файл attendable.rb и commentable.rb в папке приложений/моделей/проблем/событий

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

И теперь, используя Концерны, ваша модель событий сводится к

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

* При использовании проблем целесообразно использовать группировку на основе "домен", а не "техническую" группировку. Группировка на основе домена похожа на "Commentable", "Photoable", "Attendable". Техническая группировка будет означать "ValidationMethods", "FinderMethods" и т.д.

  • 6
    Таким образом, проблемы являются просто способом использования наследования или интерфейсов или множественного наследования? Что плохого в создании общего базового класса и создании подклассов из этого общего базового класса?
  • 3
    Действительно, @Chloe, я кое-где где-то красное, приложение Rails с каталогом «концертов» на самом деле «беспокойство» ...
Показать ещё 3 комментария
87

Стоит упомянуть, что использование проблем считается плохой идеей для многих.

Некоторые причины:

  • Существует некоторая темная магия, происходящая за кулисами. Концерн исправляет метод include, существует целая система обработки зависимостей - слишком сложная задача для чего-то, что тривиально-хороший старый шаблон Ruby mixin.
  • Ваши классы не менее сухие. Если вы заполняете 50 общедоступных методов в разных модулях и включаете их, ваш класс по-прежнему имеет 50 общедоступных методов, это просто то, что вы скрываете этот запах кода, вроде как помещать ваш мусор в ящики.
  • Codebase на самом деле сложнее ориентироваться со всеми этими проблемами.
  • Вы уверены, что все члены вашей команды имеют такое же понимание, что на самом деле должно заменить беспокойство?

Обеспокоенность - это простой способ стрелять в ногу, быть осторожным с ними.

  • 1
    Я знаю, что SO не лучшее место для этой дискуссии, но какой другой тип Ruby mixin держит ваши уроки сухими? Кажется, что причины № 1 и № 2 в ваших аргументах противоречат друг другу, разве вы просто выдвигаете аргумент в пользу улучшения дизайна ОО, уровня услуг или чего-то еще, что я упускаю? (Я не согласен - я предлагаю добавить альтернативы, помогает!)
  • 2
    Использование github.com/AndyObtiva/super_module - это один из вариантов, а использование старых добрых шаблонов ClassMethods - это еще один вариант. И использование большего количества объектов (например, сервисов) для чистого разделения проблем - это определенно правильный путь.
Показать ещё 5 комментариев
54

Этот пост помог мне понять проблемы.

# app/models/trader.rb
class Trader
  include Shared::Schedule
end

# app/models/concerns/shared/schedule.rb
module Shared::Schedule
  extend ActiveSupport::Concern
  ...
end
Показать ещё 1 комментарий
31

Я чувствовал, что большинство примеров здесь демонстрируют мощь module а не то, как ActiveSupport::Concern добавляет ценность module.

Пример 1. Более читаемые модули.

Так что без проблем это, как типичный 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

После рефакторинга с 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

Вы видите, что методы экземпляра, методы класса и включенный блок менее грязны. Проблемы будут вводить их соответственно для вас. Это одно из преимуществ использования ActiveSupport::Concern.


Пример 2: Изящная обработка зависимостей модуля.

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

В этом примере Bar - это модуль, который действительно нужен Host. Но так как Bar зависит от Foo класс Host должен include Foo (но подождите, почему Host хочет знать о Foo можно ли этого избежать?).

Таким образом, Bar добавляет зависимость везде, где он есть. И ** порядок включения также имеет значение здесь. ** Это добавляет много сложности/зависимости к огромной базе кода.

После рефакторинга с 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

Теперь это выглядит просто.

Если вы думаете, почему мы не можем добавить зависимость Foo в сам модуль Bar? Это не сработает, так как method_injected_by_foo_to_host_klass должен быть введен в класс, включая Bar не сам модуль Bar.

Источник: Rails ActiveSupport :: Концерн

6

В отношении make file filename.rb

Например, я хочу в своем приложении, где атрибут create_by существует обновление там значение на 1 и 0 для 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

после этого включите в свою модель:

class Role < ActiveRecord::Base
  include TestConcern
end

Ещё вопросы

Сообщество Overcoder
Наверх
Меню