Методы класса включаются только один раз при использовании Concern в Rails 3 с моделями с пространством имен

У меня есть структура папок, которая выглядит следующим образом:

app/models/
    concerns/
        quxable.rb
    foo/
        bar.rb
        baz.rb

Я использую Rails 3, поэтому я автоматически загрузил свои проблемы:

config.autoload_paths += Dir[Rails.root.join('app', 'models', "concerns", '**/')]

А файлы такие:

quxable.rb

module Quxable
    extend ActiveSupport::Concern        

    module ClassMethods
        def new_method
        end
    end
end

bar.rb

class Foo::Bar < ActiveRecord::Base
    include Quxable
end

баз.рб

class Foo::Baz < ActiveRecord::Base
    include Quxable
end

Теперь в консоли, если это сделать, я получаю следующие результаты:

Foo::Bar.respond_to? :new_method #=> true
Foo::Baz.respond_to? :new_method #=> false
reload!
Foo::Baz.respond_to? :new_method #=> true
Foo::Bar.respond_to? :new_method #=> false

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

ActiveRecord::Base.descendants.select{ |c| c.included_modules.include?(Quxable) }.map(&:name)

Я получаю ["Foo::Bar", "Foo::Baz"].

Есть идеи, что здесь происходит? Я предполагаю что-то с автозагрузкой/нетерпеливой загрузкой, но я не уверен, почему обе модели не получают новый метод класса.

PS — я пробовал переписать модуль без ActiveSupport::Concern (просто потому, что у меня старая версия Rails и я делаю снимки в темноте), используя:

def include(base)
    base.send :extend, ClassMethods
end

но у меня все еще та же проблема.

ИЗМЕНИТЬ

Сначала я упустил это (просто попытался представить простейшую проблему), поэтому прошу прощения у тех, кто пытался помочь ранее. Но quxable.rb на самом деле выглядит так:

module Quxable
    extend ActiveSupport::Concern 

    LOOKUP = {
        Foo::Bar => "something",
        Foo::Baz => "something else"
    }

    module ClassMethods
        def new_method
        end
    end
end

Итак, я предполагаю, что создал какую-то циклическую зависимость, определяющую константу с объектами класса. Кто-нибудь может подтвердить? Странно, что он просто молча терпит неудачу, не определяя методы класса в классе, к которому обращаются вторым. Я не знаю, почему это?


person Gray Kemmey    schedule 11.12.2015    source источник
comment
У ваших классов есть .rb в конце их имен?   -  person BroiSatse    schedule 11.12.2015
comment
Нет, опечатка. Я исправлю это.   -  person Gray Kemmey    schedule 11.12.2015
comment
Я только что создал фиктивное приложение с кодом, который вы отправили, не могу воспроизвести проблему. Есть ли шанс, что вы могли бы показать свой фактический модуль и классы?   -  person BroiSatse    schedule 11.12.2015
comment
Извините, я не могу. Я скажу, что упустил одну сложность в том, что Foo::Bar и Foo::Baz фактически расширяют другой базовый класс, который открывает соединение со сторонней базой данных, как описано здесь: ilikestuffblog.com/2012/09/21/ Но этот базовый класс не включает миксин и не делает ничего особенного, кроме подключения к внешней базе данных.   -  person Gray Kemmey    schedule 11.12.2015
comment
Такой поиск проблематичен. Он создаст экземпляр Foo::Bar до того, как модуль будет завершен. Таким образом, new_method будет опущен. Традиция в этом случае состоит в том, чтобы использовать строки в поиске и .constantize их, чтобы превратить их в классы, когда это необходимо.   -  person A Fader Darkly    schedule 11.12.2015
comment
Но я предлагаю вам использовать класс, модуль и структуру папок, которые я описываю в своем ответе, так как это не требует изменений в конфигурации Rails и, следовательно, не укусит вас за задницу позже.   -  person A Fader Darkly    schedule 11.12.2015


Ответы (2)


Судя по вашему редактированию, этот код проблематичен:

LOOKUP = {
    Foo::Bar => "something",
    Foo::Baz => "something else"
}

Он создаст экземпляр Foo::Bar до того, как модуль будет завершен. Таким образом, new_method будет опущено. Традиция в этом случае состоит в том, чтобы использовать строки в поиске и .constantize их, чтобы превратить их в классы, когда это необходимо.

LOOKUP = {
    "Foo::Bar" => "something",
    "Foo::Baz" => "something else"
}

тогда

LOOKUP.keys.first.constantize.new_method

or

result = LOOKUP[Foo::Bar.name]

использовать его.

person A Fader Darkly    schedule 11.12.2015
comment
Это то, что я сделал. Спасибо за работу со всех возможных ракурсов, лол! - person Gray Kemmey; 11.12.2015

Я думаю, что у вас опечатка, и проблемы включают в себя некоторую магию, которая позволяет вам превзойти ограничения на примеси.

Кроме того, если вы работаете в каталоге с чем-то, что уже загружено автоматически, например с «моделями», просто добавьте пространство имен к этому имени каталога.

Попробуй это:

module Concerns
  module Quxable

    extend ActiveSupport::Concern

    included do
      def self.new_method
      end
    end
  end
end


module Foo
  class Baz < ActiveRecord::Base
    include Concerns::Quxable
  end
end

Насколько я помню, вам не нужна дополнительная директива автозагрузки, так как просто будет работать использование пространства имен в каталоге под моделями.


Изменить после комментариев:

Я создал проект Rails со следующими добавленными файлами:

приложение/модели/foo/doer.rb

приложение/модели/foo/thinker.rb

приложение/модели/проблемы/thingable.rb

thingable.rb это:

module Concerns
  module Thingable
    extend ActiveSupport::Concern
    included do
      def self.thing
      end
    end
  end
end

doer.rb это:

module Foo
  class Doer < ActiveRecord::Base
    include Concerns::Thingable
  end
end

thinker.rb это:

module Foo
  class Thinker < ActiveRecord::Base
    include Concerns::Thingable
  end
end

В консоли:

Загрузка среды разработки (Rails 3.2.22)

2.1.3 :001 › Foo::Doer.respond_to? :вещь

=› правда

2.1.3 :002 › Foo::Thinker.respond_to? :вещь

=› правда

2.1.3 :003 › перезагрузить!

Перезагрузка...

=› правда

2.1.3 :004 › Foo::Doer.respond_to? :вещь

=› правда

2.1.3 :005 › Foo::Thinker.respond_to? :вещь

=› правда

2.1.3 :006 >

Я вообще не менял автозагрузку, я полагался на Rails для поиска файлов на основе пространства имён. (Используйте пространство имен для каталогов в известных каталогах, таких как «модели»)

Я бы сбросил вашу автозагрузку по умолчанию, а затем использовал соглашения Rails для расположения файлов и пространства имен. Если это не сработает, возможно, ваш проект делает что-то еще, о чем я не знаю.

Дайте мне знать, если вы можете предоставить более подробную информацию.

person A Fader Darkly    schedule 11.12.2015
comment
Я не думаю, что мне нужно module Concerns. Примеры этого не делают. Также вы можете использовать module ClassMethods... для определения метода класса с проблемами. Кроме того, я попытался пропустить миксин ActiveSupport::Concern - та же проблема. И я почти уверен, что моя настройка автозагрузки верна в том смысле, что мои модели и задачи загружаются. Но что-то не так в том, что только первая доступная модель получает метод класса, хотя обе включают Concern. - person Gray Kemmey; 11.12.2015
comment
По моему опыту, определение модулей и классов в одной строке с использованием синтаксиса :: может привести к ошибкам в дальнейшем. Лучше всего определять их в отдельных строках. - person A Fader Darkly; 11.12.2015
comment
В настоящее время я широко использую проблемы в проекте, использующем пространство имен «проблемы», и он отлично работает. Соглашение Rails заключается в том, что если вы используете папку в автоматически загружаемой папке, такой как «модели» или «контроллеры», то Rails знает, что нужно искать там из-за пространств имен. - person A Fader Darkly; 11.12.2015
comment
tl;dr: вы пробовали мое предложение или просто ковырялись в дырках? ;) - person A Fader Darkly; 11.12.2015
comment
У меня есть ощущение, что ваша настройка автозагрузки может конфликтовать с Rails, отсюда и проблемы. Мне кажется, что вы говорите, что это идет вразрез с соглашением, что причинит много боли в стране Rails. - person A Fader Darkly; 11.12.2015
comment
Никаких кубиков, явно определяющих как проблему, так и классы внутри модуля и отключающих автозагрузку. Но я не знаю, что использование автозагрузки противоречит соглашениям Rails. Автозагрузка app/models/concerns — это поведение по умолчанию в Rails 4, я просто сделал это сам, потому что я на 3. - person Gray Kemmey; 11.12.2015
comment
Также вы можете использовать модуль ClassMethods... для определения метода класса с помощью Concerns - Да, вы можете. Однако я предпочитаю использовать тот же синтаксис, который вы используете в классе, что позволяет ускорить рефакторинг и уменьшить количество ошибок рефакторинга при вырезании и вставке кода. :) - person A Fader Darkly; 11.12.2015
comment
Я даже не знал, что Rails 3 поддерживает Concerns. Вместо этого вы можете попробовать использовать github.com/chemica/augmentations-gem и посмотреть, работает лучше? - person A Fader Darkly; 11.12.2015
comment
Кроме того, если у меня будет минутка, я попытаюсь воссоздать вашу проблему в Rails3 и посмотреть, что произойдет... - person A Fader Darkly; 11.12.2015
comment
Да, они есть, просто не такие стандартные, как Rails 4. Но я попытался просто сделать из него обычный миксин, не используя никаких ActiveSupport::Concern удобств, те же проблемы. Но круто, спасибо за попытку! - person Gray Kemmey; 11.12.2015
comment
Обновленный ответ на основе тестирования с использованием Rails 3 - person A Fader Darkly; 11.12.2015
comment
Обновлено, чтобы отразить более одной модели - person A Fader Darkly; 11.12.2015
comment
Давайте продолжим обсуждение в чате. - person Gray Kemmey; 11.12.2015