开发者

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.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜