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?
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
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.
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