Як використати проблеми в Rails 4


628

Генератор проектів Rails 4 за замовчуванням тепер створює каталог "проблеми" під контролерами та моделями. Я знайшов пояснення щодо використання проблем з маршрутизацією, але нічого про контролери та моделі.

Я впевнений, що це стосується нинішньої "тенденції DCI" у громаді, і я хотів би спробувати.

Питання полягає в тому, як я повинен використовувати цю функцію, чи існує угода про те, як визначити ієрархію імен / класів для того, щоб вона працювала? Як я можу включити проблему до моделі чи контролера?

Відповіді:


617

Тож я сам це з’ясував. Це насправді досить проста, але потужна концепція. Це стосується повторного використання коду, як у наведеному нижче прикладі. В основному, ідея полягає у витягуванні загальних та / або конкретних контекстних шматочків коду, щоб очистити моделі та уникнути їх надто жирності та безладу.

Як приклад, я наведу один добре відомий шаблон, теговальний шаблон:

# 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 ми будемо пропонувати програмістам використовувати проблеми з додатком / моделями / проблемами та каталогами додатків / контролерів / проблем, які автоматично є частиною шляху завантаження. Разом із обгорткою ActiveSupport :: Concern, це достатньо підтримки, щоб цей механізм факторизації легкої ваги сяяв.


11
DCI має справу з контекстом, використовує ролі як ідентифікатори для відображення розумової моделі / випадку використання коду та не вимагає використання обгортків (методи прив’язані безпосередньо до об'єкта під час виконання), тому це насправді не має нічого спільного з DCI.
ciscoheat

2
@yagooar навіть включення його під час виконання не зробить це DCI. Якщо ви хочете побачити приклад реалізації рубінового DCI. Погляньте або на fulloo.info, або на приклади на github.com/runefs/Moby, або на те, як використовувати бордовий бар, щоб зробити DCI в Ruby і що DCI - runefs.com (Що таке DCI. нещодавно розпочато)
Rune FS

1
@RuneFS && ciscoheat ви обидва мали рацію. Я просто знову проаналізував статті та факти. І я пішов минулими вихідними на конференцію Ruby, де одна розмова про DCI, і нарешті я зрозумів трохи більше про її філософію. Змінив текст, щоб він взагалі не згадував DCI.
yagooar

9
Варто згадати (і, мабуть, включити в приклад), що методи класу повинні визначатися в спеціально названому модулі ClassMethods, і що цей модуль також розширений базовим класом be ActiveSupport :: Concern.
підступ

1
Дякую за цей приклад, головним чином, я працював глупо і визначав свої методи рівня класу всередині модуля ClassMethods з self.with все ще, і це не працює = P
Ryan Crews

379

Я читав про використання модельних проблем для моделей жиру на шкірі, а також ПУШИТИ свої коди моделей. Ось пояснення з прикладами:

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 у додатку / моделях / проблемах.

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файл в / моделі / проблем / папки подій додатки

відвідуваність.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". Технічне групування буде означати "Валідаційні методи", "FinderMethods" тощо


6
Отже, стурбованість - це лише спосіб використання успадкування чи інтерфейсів чи багаторазового успадкування? Що не так у створенні загального базового класу та підкласи з цього загального базового класу?
Хлоя

3
Дійсно @Chloe, я десь там, де червоний, додаток Rails із каталогом «турботи» насправді є «проблемою» ...
Ziyan Junaideen

Ви можете використовувати блок "включений" для визначення всіх своїх методів і включає: методи класу (з def self.my_class_method), методи екземпляра та виклики методів та директиви в області класу. Не потрібноmodule ClassMethods
Fader Darkly

1
Проблема, яка у мене виникає, полягає в тому, що вони додають функціональність безпосередньо моделі. Отже, якщо два питання стосуються обох реалізацій add_item, наприклад, ви накручені. Я пам’ятаю, що думали, що рейли були зламані, коли деякі валідатори перестали працювати, але хтось реалізував any?стурбований Я пропоную інше рішення: використовувати концерну, як інтерфейс на іншій мові. Замість визначення функціональності він визначає посилання на окремий екземпляр класу, який обробляє цю функціональність. Тоді у вас є менші, акуратніші класи, які займаються однією справою ...
A Fader Darkly

@aaditi_jain: Виправте невеликі зміни, щоб уникнути помилок. тобто «Створення attendable.rd і commentable.rb файл в / моделі / проблем / папки подій додатки» -> attendable.rd повинен бути attendable.rb Спасибі
Rubyist

97

Варто зазначити, що використання турбот багатьма вважається поганою ідеєю.

  1. як цей хлопець
  2. і ця

Деякі причини:

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

Турботи - це простий спосіб застрелити себе в ногу, будьте обережні з ними.


1
Я знаю, що ТАК не найкраще місце для цієї дискусії, але який ще тип міксу Рубін зберігає ваші заняття сухими? Схоже, причини №1 та №2 у ваших аргументах є протилежними, якщо ви просто не робите справи для кращого дизайну ОО, рівня послуг або чогось іншого, чого мені не вистачає? (Я не згоден - пропоную додавати альтернативи допомагає!)
toobulkeh

2
Використання github.com/AndyObtiva/super_module - це один варіант, а старі добрі шаблони ClassMethods - інший. А використання більшої кількості об’єктів (як-от сервісів) для чіткого розділення проблем - це безумовно шлях.
Dr.Strangelove

4
Нахил, тому що це не відповідь на питання. Це думка. Це думка, на яку я впевнений, що в цьому є свої достоїнства, але це не повинно бути відповіддю на питання про StackOverflow.
Адам

2
@Adam Це впевнена відповідь. Уявіть, що хтось запитає, як використовувати глобальні змінні в рейках, безумовно, згадати, що існують кращі способи, як щось робити (наприклад, Redis.current vs $ redis) може бути корисною інформацією для початківців тем? Розробка програмного забезпечення по суті є впевненою дисципліною, не обійти її. Насправді я розглядаю думки як відповіді та дискусії, відповідь яких найкраща за весь час у
потоковому потоці

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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.