Как динамически генерировать имена ассоциаций?

Я использую Ruby on Rails 3.2.2 и самоцвет Squeel. У меня есть следующие заявления, и я пытаюсь рефакторинг my_squeel_query метод в модуле Mixin (так как он используется многими моими моделями):

# Note: 'article_comment_associations' and 'model_as_like_article_comment_associations'
# refer to database table names.

class Article < ActiveRecord::Base
  def my_squeel_query
    commenters.
      .where{
        article_comment_associations.article_id.eq(my{self.id}) & ...
      }
  end
end

class ModelAsLikeArticle < ActiveRecord::Base
  def my_squeel_query
    commenters.
      .where{
        model_as_like_article_comment_associations.article_id.eq(my{self.id}) & ...
      }
  end
end

Моя проблема в том, что я не могу рефакторинг article_comment_associations а также model_as_like_article_comment_associations заявления путем генерации динамического имени в модуле Mixin. То есть если бы это было String Я мог бы динамически генерировать связанное имя, используя что-то вроде "#{self.class.to_s.singularize}_comment_associations" в дальнейшем:

class Article < ActiveRecord::Base
  include MyModule
end

class ModelAsLikeArticle < ActiveRecord::Base
  include MyModule
end

module MyModule
  def my_squeel_query
    commenters.
      .where{
        # Note: This code doesn't work. It is just an sample.
        "#{self.class.to_s.singularize}_comment_associations".article_id.eq(my{self.id}) & ...
      }
  end
end

Но, поскольку это не мой случай, я не могу "построить" имя и сделать my_squeel_query быть "разделенным" между моделями.

Как я могу динамически генерировать имена ассоциаций, связанные с самоцветом Squeel? Стоит ли думать о рефакторинге по-другому? О чем вы советуетесь?

3 ответа

Решение

Поскольку DSL является instance_evaled, вы можете сказать что-то вроде:

def my_squeel_query
  base = self
  commenters.
    .where{
      # Note: This code does work. Because it's awesome.
      __send__("#{base.class.to_s.singularize}_comment_associations").
        article_id.eq(my{self.id})
    }
end

Вы можете сделать это, если вы генерируете методы динамически. Module.included Для этого предусмотрен метод:

module ModuleAsLikeArticle
  def self.included(base)
    base.send(:define_method, "#{base.to_s.singularize}_comment_associations") do
      # ...
    end
  end
end

Это срабатывает, когда модуль импортируется с include и позволяет создавать методы, специально предназначенные для этого.

Как примечание вы можете использовать base.name.underscore.singularize для более удобочитаемого имени метода. По соглашению имена методов не должны содержать заглавных букв, особенно в качестве первого символа.

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

module ModuleAsLikeArticle
  def has_comments
    base.send(:define_method, "#{base.to_s.singularize}_comment_associations") do
      # ...
    end
  end
end

Это будет использоваться так:

class ModelAsLikeArticle < ActiveRecord::Base
  extend MyModule

  has_comments
end

Так как метод не создан до has_comments называется, вы можете смело продлевать ActiveRecord::Base и затем вставьте соответствующий вызов во все классы, которым требуется эта функциональность.

Я думаю, что вы можете найти то, что вам нужно в Rails Reflection класс (http://api.rubyonrails.org/classes/ActiveRecord/Reflection/ClassMethods.html), который, как говорится на странице, позволяет запрашивать классы ActiveRecord об их ассоциациях и объединениях.

Другие вопросы по тегам