开发者

Rails: polymorphism vs. mixins vs. controller voodoo?

开发者 https://www.devze.com 2023-04-10 03:23 出处:网络
So, here\'s the short of it, and it\'s sort of a newbie question, so please bear with me...I\'m building a bulletin-board type app as a Rails proof of concept app for my corp.

So, here's the short of it, and it's sort of a newbie question, so please bear with me... I'm building a bulletin-board type app as a Rails proof of concept app for my corp.

The bulletin board is replacing existing functionality, so the constraints are pretty tight. The biggest constraint is that there are three levels of content: boards, topics, and replies. You can't reply to a reply (at least, the system doesn't assist you with that) -- this is intentional and beyond the scope of this question.

Now, topics and replies have common features: they are both user-generated content, they can both be moderated, they have body text, they are posted by a user, etc. A coworker suggested that I simply make these objects be specializations of a concept called a "post" and then use this post for the bulle开发者_开发百科tin board too (since the board also sort of has a title and content... long story).

So I've built the app with a post model, and I've done everything else with controller and routes magic; I have a TopicsController and a BoardsController (reply logic is handled inside the TopicsController via custom actions). I've built nested namespaces in my routes file so that I can do: http://foo.com/boards/1/topics/2 etc.

The coworker (who had experience with Rails when I didn't, so I took him at his word) claimed that I should do this pseudo-polymorphic design because we would ultimately have other content -- product reviews, for instance -- that would have similar content (user-generated text, the potential to be moderated) -- and it would be much easier if we could simply tie back to one database table.

He also suggested that instead of polymorphism, I should implement the "extra" functionality in, e.g., product reviews, via module mixins. I'm not sure that this is an improvement over simple polymorphism, or over polymorphism implemented via table joins (where the extra information is tied in via a db relationship).

But as this build gets larger, I'm having doubts about this. It just... feels wrong. First of all, while there IS polymorphism in the model, I'm not 100% sure what the best implementation is; in some ways there are common elements of topics and replies -- both have content and can be moderated -- but in other ways they are very different (replies don't have subjects, for instance). And boards aren't even created by end-users; they are moderator-created. Product reviews aren't like topics or replies at all; they instead are tied to a particular product.

The more I build this out, the more I feel like we have the "all you have is a hammer" syndrome; we saw that topics and replies and boards and product reviews all looked "kinda similar," so we're trying to force them into an inheritance tree. And in fact, I'm finding myself having to fight harder and harder AGAINST the Rails framework to add custom routes and actions, to override default model properties, and so on.

So, my question is this: what is the community's opinion on this? Should we simply have separate tables and models for boards, topics, and replies?


My experiences is that in general using polymorphism in rails is almost always a mistake. Almost every time I've seen someone start with it, they end up pulling it out later.

What I've come to believe is that the simpler the model, in general the better. So for this type of approach I'd recommend having three ActiveRecord classes -- Boards, Topics, and Replies.

Also, regarding nesting your name spaces -- are you implementing that in your routes file as a nested resource? I'd recommend that over using namespaces. Specifically, I'd recommend:

resources :boards do
  resource :topics
end    

Running rake routes this gives you the following routes:

     board_topics POST   /boards/:board_id/topics(.:format)      {:action=>"create", :controller=>"topics"}
 new_board_topics GET    /boards/:board_id/topics/new(.:format)  {:action=>"new", :controller=>"topics"}
edit_board_topics GET    /boards/:board_id/topics/edit(.:format) {:action=>"edit", :controller=>"topics"}
                  GET    /boards/:board_id/topics(.:format)      {:action=>"show", :controller=>"topics"}
                  PUT    /boards/:board_id/topics(.:format)      {:action=>"update", :controller=>"topics"}
                  DELETE /boards/:board_id/topics(.:format)      {:action=>"destroy", :controller=>"topics"}
           boards GET    /boards(.:format)                       {:action=>"index", :controller=>"boards"}
                  POST   /boards(.:format)                       {:action=>"create", :controller=>"boards"}
        new_board GET    /boards/new(.:format)                   {:action=>"new", :controller=>"boards"}
       edit_board GET    /boards/:id/edit(.:format)              {:action=>"edit", :controller=>"boards"}
            board GET    /boards/:id(.:format)                   {:action=>"show", :controller=>"boards"}
                  PUT    /boards/:id(.:format)                   {:action=>"update", :controller=>"boards"}
                  DELETE /boards/:id(.:format)                   {:action=>"destroy", :controller=>"boards"}

In general, whenever you find yourself fighting against the rails framework, that's a clear signal you're doing things the hard way. Nice job sensing that -- and go with your gut. Keep it simple.


I would definitely keep boards and product reviews as separate database tables with stand-alone models and controllers. They are really quite a bit different from each other and different than topics and replies. Features like user-generated content and moderation don't depend on polymorphism. As long as the column names are the same, the models are essentially automatically duck-typed. You can just include the moderation functionality as controller and/or model module mix-ins and assume the object in question will have the appropriate attributes for moderation to work.

Topics and replies, on the other hand, seem like a prime candidate for Polymorphism (specifically, I'd use Single Table Inheritance). If I understand correctly, these topics and replies are nearly identical in every fashion, so polymorphism will save you some work and prevent errors (by keeping things DRY).

Generally, it works like you said. If you feel like you are fighting against the framework's opinions, you're not really taking full advantage of the framework, and you should probably restructure your design to fight less.

Disclaimer: I don't claim to be an expert in Rails, but I'm not a novice either -- I do have the "ruby-on-rails" stackoverflow badge, after all ;). I have written several apps, including two that heavily take advantage of polymorpism and module mix-ins, so I have had quite a bit experience grappling with this particular type of problem.


First off, it's probably a good idea to look for suitable Gems -- there are tons of Gems out there which can simplify your code / coding. e.g. acts_as_commentable, acts_as_taggable , ancestry, etc..

The answer to your question is: it depends..

If you have a certain flow which needs to be imposed -- e.g. you need to have a topic before you can have a comment, you need to have a comment before you have a reply.. etc.

Using a polymorphic self-referential association could work, but it makes your design more complicated. Maybe it's better to do the straight-forward implementation where each of them is their own model, and then refine it later if needed.

You might also want to look a the "ancestry" gem to see how they model the ancestry chain for fast access to both parents and children.

Check these RailsCasts:

ooops ... RailsCasts is currently down... to be continued...

Episode 163: Self Referential Associations

Check http://www.railscasts.com/ -- there are tons of examples for "Blog Posts with Comments and Replies" type applications

0

精彩评论

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