Ruby Constants in Nested classes. Metaprogramming
Q: how to share information in nested classes about parent class
class Post
MODEL = self
extend PostMod
has_many :comments, :class_name => 'Post::Comment'
class Comment
include CommentMod
belongs_to :model, :class_name => "Post"
def method_in_comment
puts MODEL
end
end
class Another
end
end
module CommentMod
def method_in_mod1
puts self.class::MODEL
end
def method_in_mod2
puts MODEL
end
end
module PostMod
def method_in_mod1
puts self::Comment
end
end
b = Post::Comment.new
b.method_in_comment # Post
b.method_in_mod1 # u开发者_JAVA技巧ninitialized constant Post::Comment::MODEL (NameError)
b.method_in_mod2 # uninitialized constant CommentMod::MODEL (NameError)
Reason of such design, in this example (real system is much more complex), is to add comments to any model by just "include module". Thats will add controllers, views and model methods
Comments behavior is similar for all models. But model may override any method in class Comment if something needs to be tunned.
Tricky part of this is, that modules doesn know anything about top classes (Post) and classes at same level (Comment and Another), but they need to call some class methods on them.
I am using class.name parsing now to get a class name of a top-level class, but there should be another ways.
Any suggestions are welcome including changing of a design.
UPDATE
Post and comments are just an example, I dont have this models in my project.
I am migrating from underscore nonation(or CamelCase) to nested classes (from ArticleTranslation to Article::Translation). That looks like more clear to me. In previos version I've used model name to call class methods on classes (on ModelTranslation, etc) Now, after refactor lib modules, I no longer need to know model_name.
But I fell into the trap: In ruby you may reopen classes, like
class Post < ActiveRecord::Base
end
class Post::Comment < ActiveRecord::Base
belongs_to :language
end
class Post::Comment::Another1 < ActiveRecord::Base
end
class Post::Comment::Another2 < ActiveRecord::Base
end
class Language < ActiveRecord::Base
has_many :post_comments, :class_name => "Post::Comment"
end
And I have an issue: if page been load immediatly after server start -- all ok but next calls to that page throws an error: no such association; calling included modules methods -- no method errors. I think, that rails loads wrong file for Post::Coment, the worst is that I cant debug this error... but this is another question.
UPDATE2
Second problem solved. Problem was in helper classes.
Can you clarify "add comments to any model by just "include module"".? Do you mean you have an ActiveRecord model Comment, and you want to polymorphically associate it to many other models, such that e.g.
Post
has_many :comments
Article
has_many :comments
...etc?
So, based on your later comments, here's something a little more like what you're after using Module.extended:
module PostMod
def self.extended(klass)
klass.send :include, InstanceMethods
end
module InstanceMethods
def method_in_mod1
puts "from PostMod#method_in_mod1: #{self.class.inspect}"
end
end
end
module CommentMod
def self.extended(klass)
klass.send :include, InstanceMethods
end
module InstanceMethods
def method_in_mod1
puts "from CommentMod#method_in_mod1: #{self.class.inspect}"
end
def method_in_mod2
puts "from CommentMod#method_in_mod2: #{self.class.ancestors.inspect}"
end
end
end
class Post
extend PostMod
class Comment
extend CommentMod
def method_in_comment
puts "from Post::Comment#method_in_comment: #{self.class.inspect}"
end
end
class Another
end
end
b = Post::Comment.new
b.method_in_comment # Post::Comment
b.method_in_mod1 # Post::Comment
b.method_in_mod2 # [Post::Comment, CommentMod::InstanceMethods, Object, Kernel]
Though the output is not exactly the same as your example. It might be difficult to share class variables in the way you're attempting because you're not actually using any inheritance, just nested classes.
Can you detail the intent for sharing those variables, perhaps there's a better way to accomplish the goal?
I have solved problem myself by refactoring code. So sharing class.name is no more needed. If I'll ever need this best is to use class.name.scan or matches to got top-level class name, then refactor code and remove class.name.scan =) Sharing a class name was a bad idea.
精彩评论