3

I need help implementing or breaking this into Single-Table-Inheritance (STI). I have read about it and I'm not quite sure yet if I'm going about this the right way. If you guys have suggestions to implement it. Or even if it's very different from what I have now, please advice.

So, normally I have this following classes (all models).

class Article < ActiveRecord::Base
  has_many :attachments

  has_many :medias
  has_one :banner

  accepts_nested_attributes :medias
  ...
end

class Attachment < ActiveRecord::Base
  belongs_to :article
end

class Media < Attachment
  default_scope { where(attachment_type: 'media') }

  def audio?; media_type == 'audio'; end
  def video?; media_type == 'video'; end

  validate :embed_url, presence: true if :video?

  def path
    if audio?
      # Different audio path
    elsif video?
      # Different video path
    end
  end

  after_commit :process_audio_file
  def process_audio_file; ...; end

  after_commit :process_video_file
  def process_video_file; ...; end
end

class Banner < Attachment
  default_scope { where(attachment_type: 'banner') }
  ...
end

And typically it would work normally too..

article = Article.first
first_media = article.medias.first
banner = article.banner

But then I noticed that Media would probably be bloated and have too many different logics with different things to do for different media_types. So I tried to separate them by doing this:

class Article < ActiveRecord::Base
  has_many :attachments

  has_many :medias
  has_one :banner

  accepts_nested_attributes_for :medias
end

class Attachment < ActiveRecord::Base
  belongs_to :article
end

class Media < Attachment
  default_scope { where(attachment_type: 'media') }
end

class AudioMedia < Media
  default_scope { where(media_type: 'audio') }

  def path
    # Audio path
  end

  after_commit :process_audio_file
  def process_audio_file; ...; end
end

class VideoMedia < Media
  default_scope { where(media_type: 'video') }

  validate :embed_url, presence: true

  def path
    # Video path
  end

  after_commit :process_video_file
  def process_video_file; ...; end
end

Now here I have separated logic from each other. Great! But now it poses few problems like:

article = Article.first
first_media = article.medias.first

In doing this, I'm only at Media class... To get to say AudioMedia class, what I have to do is:

"#{first_media.media_type}Media".constantize.find(first_media.id)

Also, for my nested_attributes to work, I'd have to define

accepts_nested_attributes_for :audio_medias
accepts_nested_attributes_for :video_medias

to make it work right? Then I'd have to define their relationships as well like:

has_many :medias
has_many :audio_medias
has_many :video_medias

Any advice? Thanks and cheers!

EDIT

Added the related tables and fields

articles
  id
  [some_other_fields]

attachments
  id
  article_id
  attachment_type # media, banner, etc...
  media_type # audio, video, etc...
  [some_other_fields]
index
  • 3,697
  • 7
  • 36
  • 55

2 Answers2

0

could you please add your migrations?

Normally, I would expect something like:

article.medias.first.class == AudioMedia #true

And is there a reason why put a Media Class in place? Just for doing the default scope, this isn't needed cause you have a separated Class AudioMedia or VideoMedia which lives in Attachments with type AudioMedia or VideoMedia. And you can easily access them by their class names.

By the way you should take a look at paperclip or carrier_wave.

Steffen Jurrack
  • 108
  • 2
  • 6
  • Yes. Normally it would be like `article.medias.first` which was mentioned on the very top and it would normally be `Media`. But I wanted to separate their logics as STI suggests. `Media` is also used to contain common methods and others for any media. – index Oct 25 '13 at 02:51
0

It looks like you are missing something important related to STI. When you create a new AR instance and your table has a column named 'type', AR will store the classname of your object in that column. When you select the record later, AR will use the value in the type column to detect the class to construct it.

It looks like you are somehow implementing something similar by using those scopes and audio? video? methods.

So first

rails g migration add_type_to_attachments type:string
rake db:migrate

Then make your code look like this:

class Article < ActiveRecord::Base
  has_many :attachments      
  accepts_nested_attributes_for :attachments
end

class Attachment < ActiveRecord::Base
  belongs_to :article
end

class Media < Attachment

end

class AudioMedia < Media

  def path
    # Audio path
  end

  after_commit :process_audio_file
  def process_audio_file; ...; end
end

class VideoMedia < Media        
  validate :embed_url, presence: true

  def path
    # Video path
  end

  after_commit :process_video_file
  def process_video_file; ...; end
end    

now all your attachments are in one field

article.attachments

If you need only videos for instance

article.attachments.select{|a|a.is_a? VideoMedia}
Mark Meeus
  • 597
  • 4
  • 11
  • Ah there is built-in for this in rails? I can't seem to find it. Can you provide a link for the document for this so I can read further? Also, if I did this and did `article.attachments.select{|a| a.is_a? VideoMedia }` would I still need to do a `VideoMedia.find(#)` to get to the actual model? Or how can I get it? – index Oct 29 '13 at 02:12