Rails: Trying to output the content of an object when you don't know what class the object belongs to
Preamble:
Don't be scared off by the fact My Rails 3 app uses the has_many_polymorphs gem as I don't think you need to be familiar with the gem in order to help me here :)
My code:
I have a Post model which has many Snippets. There are four additional models which are snippetable i.e. of type Snippet:
class Post < ActiveRecord::Base
has_many_polymorphs :snippets,
:from => [:texts, :videos, :images, :codes],
:through => :snippets
end
class Snippet < ActiveRecord::Base
belongs_to :post
belongs_to :snippetable, :polymorphic => true
attr_accessible :post_id, :snippetable_type, :snippetable_id
end
# There following four models are snippetable:
class Code < ActiveRecord::Base
# note that the attribute name is the same as the Class name
attr_accessible :code
end
class Text < A开发者_如何学PythonctiveRecord::Base
# note that the attribute name is the same as the Class name
attr_accessible :text
end
class Image < ActiveRecord::Base
# note that the attribute name is the same as the Class name
attr_accessible :image
end
class Video < ActiveRecord::Base
# note that the attribute name is the same as the Class name
attr_accessible :video
end
Adding snippets to a Post
Now if I want to add two text snippets and two image snippets to a post I can do this:
# find the first post
p = Post.first
p.texts << Text.create(:text => "This is the first sentence")
p.images << Image.create(:image => "first_image.jpg")
p.texts << Text.create(:text => "This is the second sentence")
p.images << Image.create(:image => "second_image.jpg")
The result is a blog post that looks like this:
- text snippet
- image snippet
- text snippet
- image snippet
My Problem
I'm having some trouble displaying the content of each snippet in my view.
I could do the following in my view:
- for text in @post.texts
= text.text
- for image in @post.images
= image.image
- for code in @post.codes
= code.code
- for video in @post.videos
= video.video
BUT this will result in a blog post that looks like this:
- text snippet
- text snippet
- image snippet
- image snippet
I don't want snippets to be grouped by Class in this way.
How can I solve this?
Well I've a look at the problem. I know that I CAN do the following:
- for snippet in @post.snippets
= snippet.snippetable_type.downcase
and this will output the Class name of each snippet as shown:
- text
- image
- text
- image
BUT I want the content of each snippet.
Expanding on what we have above, since each type of snippet has one attribute with the same name as the Class itself, I can also do this:
- for snippet in @post.snippets
= "#{snippet.snippetable_type.downcase}.#{snippet.snippetable_type.downcase}"
And this will output the classname AND the attribute name of each snippet:
- text.text
- image.image
- text.text
- image.image
If I could just find a way of getting to the content and not the Classnames then I'd be ok. Anyone got any clue?
I will be absolutely amazed if anyone gets this one. Thanks if you have read this far.
So you want it in the order that for snippet in @post.snippets
gives you, but you need it to send text, image, code or video depending on the snippetable_type
, or am I misreading you?
- for snippet in @post.snippets
= snippet.snippetable.send(snippet.snippetable_type.downcase)
I'm wondering if either (a) there's more complexity to this app and these snippets than you're showing, or (b) you're using has_many_polymorphs in a situation that doesn't warrant the overhead.
What I mean is: if these snippet types are really all identical except for the class name and accessor name, then you don't need the subclasses at all: one generic Post class will do.
On the other hand, if the Post/Snippet classes do have different behavior depending on type, then a better solution would be to use duck typing to get the output you want. (And this may actually be the entire purpose of has_many_polymorphs if I understand it from your code.) For example: Each snippetable type could implement a method (the duck type) .to_html() in which each one would create a bare-bones html presentation most appropriate to it. And then this is the method you'd call inside your loop.
精彩评论