How to dynamically generate association names?

时光毁灭记忆、已成空白 提交于 2020-01-14 04:25:08

问题


I am using Ruby on Rails 3.2.2 and the Squeel gem. I have following statements and I am trying to refactoring the my_squeel_query method in a Mixin module (since it is used by many of my models):

# 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

My problem is that I can not refactoring article_comment_associations and model_as_like_article_comment_associations statements by generating a dynamic name in the Mixin module. That is, if that was a String I could dynamically generate the related name by using something like "#{self.class.to_s.singularize}_comment_associations" as the following:

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

But, since it is not my case, I cannot "build" the name and make the my_squeel_query to be "shared" across models.

How can I dynamically generate association names related to the Squeel gem? Should I think to refactoring in another way? What do you advice about?


回答1:


Since the DSL is instance_evaled, you can actually say something like:

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



回答2:


You can do this if you generate the methods dynamically. The Module.included method is provided for this purpose:

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

This gets triggered when the module is imported with include and allows you to create methods specifically tailored for that.

As a note you might want to use base.name.underscore.singularize for a more readable method name. By convention, method names should not have upper-case in them, especially not as the first character.

Conventional Rails type applications use a different approach, though, instead defining a class method that can be used to create these on-demand:

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

This would be used like this:

class ModelAsLikeArticle < ActiveRecord::Base
  extend MyModule

  has_comments
end

Since the method is not created until has_comments is called, you can safely extend ActiveRecord::Base and then insert the appropriate call in all the classes which require that functionality.




回答3:


I think you might find what you need in the Rails Reflection class (http://api.rubyonrails.org/classes/ActiveRecord/Reflection/ClassMethods.html), which, as the page says, allows you to interrogate ActiveRecord classes about their associations and aggregations.



来源:https://stackoverflow.com/questions/12604949/how-to-dynamically-generate-association-names

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!