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

628

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

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

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

yagooar
источник

Ответы:

617

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

В качестве примера я приведу один известный шаблон 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

Таким образом, следуя примеру Product, вы можете добавить Taggable для любого класса и поделиться его функциональностью.

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

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

yagooar
источник
11
DCI имеет дело с контекстом, использует роли в качестве идентификаторов для сопоставления ментальной модели / варианта использования с кодом и не требует использования оболочек (методы привязываются непосредственно к объекту во время выполнения), так что это на самом деле не имеет ничего общего с DCI.
ciscoheat
2
@yagooar, даже если он будет включен во время выполнения, не сделает его DCI. Если вы хотите увидеть пример реализации ruby ​​DCI. Взгляните либо на fulloo.info, либо на примеры на github.com/runefs/Moby, либо на то, как использовать maroon для создания DCI в Ruby и что такое DCI runefs.com (что такое DCI. только началось недавно)
Rune FS
1
@RuneFS && ciscoheat вы оба были правы. Я просто снова проанализировал статьи и факты. И в прошлые выходные я отправился на конференцию по Ruby, где один из выступлений был о DCI, и, наконец, я немного больше понял ее философию. Изменен текст, чтобы он вообще не упоминал DCI.
yagooar
9
Стоит отметить (и, возможно, включить в пример), что методы класса должны быть определены в специально названном модуле ClassMethods, и что этот модуль расширен базовым классом также ActiveSupport :: Concern.
febeling
1
Спасибо за этот пример, в основном, потому что я был тупым и определял свои методы уровня класса внутри модуля ClassMethods с помощью self.wh чем бы то ни было, и это не работает = P
Райан Крюс
379

Я читал об использовании проблем с моделями для определения размеров кожи, а также для сушки ваших кодов моделей. Вот объяснение с примерами:

1) СУШКА кодов моделей

Рассмотрим модель Article, модель Event и модель Comment. Статья или событие имеет много комментариев. Комментарий относится к статье или событию.

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

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

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

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

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

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 файл папке app / models / беспокойства / события

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». Техническая группировка будет означать «Методы проверки», «Методы поиска» и т. Д.

Аадити Джайн
источник
6
Таким образом, проблемы являются просто способом использования наследования или интерфейсов или множественного наследования? Что плохого в создании общего базового класса и создании подклассов из этого общего базового класса?
Хлоя
3
Действительно @Chloe, я кое-где где-то красное, приложение Rails с каталогом «
концертов» на
Вы можете использовать блок «включенный» для определения всех ваших методов и включает в себя: методы класса (с def self.my_class_method), методы экземпляра и вызовы методов и директивы в области видимости класса. Не нужноmodule ClassMethods
Фейдер мрачно
1
Проблема, с которой я сталкиваюсь, заключается в том, что они добавляют функциональность непосредственно в модель. Так что, если два вопроса касаются обоих add_item, например, вы ввернуты. Я помню, как думал, что Rails был сломан, когда некоторые валидаторы перестали работать, но кто-то реализовал any?в этом проблему. Я предлагаю другое решение: используйте проблему как интерфейс на другом языке. Вместо определения функциональности он определяет ссылку на отдельный экземпляр класса, который обрабатывает эту функциональность. Тогда у вас есть более мелкие, аккуратные классы, которые делают одно ...
A Fader Darkly
@aaditi_jain: пожалуйста, исправьте небольшое изменение, чтобы избежать неправильного представления. то есть «Создайте файл Participable.rd и commentable.rb в папке app / models / Concers / Event» -> Participable.rd должен быть в форме visible.rb Спасибо
Rubyist
97

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

  1. как этот парень
  2. и этот

Несколько причин:

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

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

Dr.Strangelove
источник
1
Я знаю, что SO не лучшее место для этой дискуссии, но какой другой тип Ruby mixin держит ваши уроки сухими? Кажется, что причины № 1 и № 2 в ваших аргументах противоречат друг другу, разве вы просто выдвигаете аргументы в пользу улучшения дизайна ОО, уровня услуг или чего-то еще, что я упускаю? (Я не согласен - я предлагаю добавить альтернативы, помогает!)
toobulkeh
2
Использование github.com/AndyObtiva/super_module - это один из вариантов, а использование старых добрых шаблонов ClassMethods - это еще один вариант. И использование большего количества объектов (например, сервисов) для чистого разделения проблем - это определенно правильный путь.
Dr.Strangelove
4
Даунтинг, потому что это не ответ на вопрос. Это мнение. Это мнение, которое, я уверен, имеет свои достоинства, но оно не должно быть ответом на вопрос о StackOverflow.
Адам
2
@ Adam Это взвешенный ответ. Представьте, что кто-то спросит, как использовать глобальные переменные в рельсах, и обязательно упомяните, что есть лучшие способы сделать что-то (например, Redis.current vs $ redis) может быть полезной информацией для начинающего темы? Разработка программного обеспечения по своей сути является дисциплинированной дисциплиной, от нее никуда не деться. На самом деле, я рассматриваю мнения как ответы и обсуждения, ответы на которые всегда самые лучшие в стеке, и это хорошо
Dr.Strangelove
2
Конечно, упоминание этого вопроса вместе с вашим ответом на вопрос выглядит нормально. Ничто в вашем ответе на самом деле не отвечает на вопрос ОП. Если все, что вы хотите сделать, - это предупредить кого-то, почему он не должен использовать проблемы или глобальные переменные, тогда это будет хорошим комментарием, который вы можете добавить к его вопросу, но на самом деле это не будет хорошим ответом.
Адам
46

Я чувствовал, что большинство приведенных здесь примеров демонстрируют силу, 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 :: Концерн

Сива
источник
Спасибо за это. Я начал задаваться вопросом, в чем их преимущество ...
Хари Карам Сингх
FWIW это примерно скопировать из документов .
Дейв Ньютон
7

В концерте сделайте файл 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

Если вы хотите передать аргументы в действии

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

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

class Role < ActiveRecord::Base
  include TestConcern
end
Саджад Муртаза
источник