views:

1231

answers:

4

Hi,

I have an issue with Ruby on Rails. I have several model classes that inherit from the same class in order to have some generic behaviour.

The parent class is called CachedElement. One of the child is called Outcome.

I want an other model, called Flow to belong to any child of CachedElement. Hence Flow has a polymorphic attributes called element, to which it belongs_to

When I create a new flow, that belongs to an Outcome, the element_type is set to "CachedElement" which is the parent class, instead of "Outcome".

This is confusing because since I have several type of CachedElement which are stored in different tables, the element_id refers to several different element.

In short I would like the element_type field to refer to the child class name and not the parent class name.

How can I do that ?

+4  A: 

The field element_type is set to the parent class because ActiveRecord expects you to use single-table inheritance when deriving from other models. The field will reference the base class because it refers to the table that each instance is stored in.

If the children of CachedElement are stored in their own tables, it may be more helpful to replace the use of inheritance with the use of Ruby modules. The standard approach for sharing logic between classes is to use mix-ins instead of inheritance. For example:

module Cacheable
  # methods that should be available for all cached models
  # ...
end

class Outcome < ActiveRecord::Base
  include Cacheable
  # ... 
end

You can now easily use polymorphic associations as you have been doing already, and element_type will be set to the proper class.

molf
Thanks, that's what I did, but it was kind of tricky to be able to inherits both instance and class methods from the module
jules
+1  A: 

Thanks, that's what I did, but it was kind of tricky to be able to inherits both instance and class methods from the module

Class methods can be done by:

module Cachable
  def self.included(base)
    base.extend(ClassMethods)
  end

  module ClassMethods
    def a_class_method
      "I'm a class method!"
    end
  end

  def an_instance_method
    "I'm an instance method!"
  end
end

class Outcome < ActiveRecord::Base
  include Cacheable
end
Thorbjørn Hermansen
Thanks for your message, that's what I did.In a Ruby on Rails project, where would you put this file: in the app/model folder, or in the lib folder ?
jules
I'm not that of a ruby on rails master, but I would have put it in lib or made a gem out of it :-)
Thorbjørn Hermansen
+1  A: 

the file should go on you lib folder. but... you could do the inheritance thing as well.

all you need to do is to tell you parent class to act as an abstract class.

# put this in your parent class then try to save a polymorphic item again.
# and dont forget to reload, (I prefer restart) if your gonna try this in
# your console.
def self.abstract_class?
  true
end

and thats pretty much it, this was kinda unespected for me and actually really hard to find in the documentation and anywhere else.

Kazuyoshi Tlacaelel.

+1  A: 

if you want to add class methods and instance methods through a mixin (Module) then I recommend you to abstract these in different modules.

module FakeInheritance

    def self.included(klass)
      klass.extend ClassMethods
      klass.send(:include, InstanceMethods)
    end

    module ClassMethods
      def some_static_method
        # you dont need to add self's anywhere since they will be merged into the right scope
        # and that is very cool because your code is more clean!
      end
    end

    module InstanceMethods
       # methods in here will be accessable only when you create an instance
    end
end

# fake inheritance with static and instance methods
class CachedElement
  include  FakeInheritance
end