Forum: Ruby How to model Hibernate multi-table inheritance with ActiveRe

Posted by Marcus Andersson (marcan)
on 2006-06-28 13:22
(Received via mailing list)
Hi

I have this "old" database mainly modeled for Hibernate. The java model
has something called Asset. Asset is inherited by SomeAsset and
AnotherAsset and the similar stuff is in the asset table and the
specific data for SomeAsset is in table some_asset and for AnotherAsset
in another_asset. This is then reflected in hibernate using a
<joined-subclass/> element.

What would be the best way to model this in ActiveRecord? I cannot
change the exisiting database structure beyond adding new columns (ie I
cannot move values from the join tables back to the main table and use
STI).

Could I implement method_missing in Asset to just delegate to methods
that might exist in SomeAsset or AnotherAsset? I only use duck typing so
from a type perspective it would be fine. This would make it hard to
override behaviour from Asset in subclasses though (since they aren't
subclasses...).

Is there another better solution?

Best Regards,

Marcus
Posted by Peter Krantz (Guest)
on 2006-06-28 14:53
(Received via mailing list)
On 6/28/06, Marcus Andersson <m-lists@bristav.se> wrote:
>
> I have this "old" database mainly modeled for Hibernate...
> [snip]

Depending on how your model looks, one idea would be to use RBatis
which is a more flexible O/R mapper. If SQL doesn't scare you that is.
See http://jutopia.tirsen.com/articles/2006/05/23 for more details.

/Peter
Posted by Alex Young (regularfry)
on 2006-06-28 15:06
(Received via mailing list)
Marcus Andersson wrote:
> change the exisiting database structure beyond adding new columns (ie I 
> cannot move values from the join tables back to the main table and use 
> STI).
> 
> Could I implement method_missing in Asset to just delegate to methods 
> that might exist in SomeAsset or AnotherAsset? I only use duck typing so 
> from a type perspective it would be fine. This would make it hard to 
> override behaviour from Asset in subclasses though (since they aren't 
> subclasses...).
> 
> Is there another better solution?
I'd have SomeAsset and AnotherAsset instances each hold a delegate Asset
object that they proxy any calls they can't answer themselves to.  I've
played around a little with trying to implement a full class table
inheritance scheme for AR on that basis, but never really had enough
need for it myself to polish it up.
Posted by Pit Capitain (Guest)
on 2006-06-28 16:31
(Received via mailing list)
Marcus Andersson schrieb:
> STI).
I don't know ActiveRecord, but are you allowed to add a (SQL) view to
your database schema? This way you might be able to use single table
inheritance. Some databases allow even DML Operations on views.


Regards,
Pit
Posted by Marcus Andersson (marcan)
on 2006-06-28 19:21
(Received via mailing list)
Alex Young skrev:
> I'd have SomeAsset and AnotherAsset instances each hold a delegate 
> Asset object that they proxy any calls they can't answer themselves 
> to.  I've played around a little with trying to implement a full class 
> table inheritance scheme for AR on that basis, but never really had 
> enough need for it myself to polish it up.
>
If I do like that I have to search in several different tables when I
want to search for assets when I don't care about the type? Or did I
misunderstand?

/Marcus
Posted by Jacob Fugal (Guest)
on 2006-06-28 19:56
(Received via mailing list)
On 6/28/06, Alex Young <alex@blackkettle.org> wrote:
> I'd have SomeAsset and AnotherAsset instances each hold a delegate Asset
> object that they proxy any calls they can't answer themselves to.  I've
> played around a little with trying to implement a full class table
> inheritance scheme for AR on that basis, but never really had enough
> need for it myself to polish it up.

I agree with Alex here. While inheritance was what the tables were
originally trying to emulate, composition was the resulting design
anyways. With Ruby and it's duck typing, inheritance isn't required
for the ability to pass a SomeAsset where an Asset is expected. Java's
static typing would require SomeAsset to "be a" Asset, but Ruby only
needs SomeAsset to "quack like" an asset, which can be covered with
judicious use of composition and method_missing. I'd actually provide
a mixin that sets up this method_missing. Example:

  class Asset < ActiveRecord::Base
    # we're leaving out the reverse relationship to the "child" classes
    # -- just as a parent class implementation wouldn't really know 
about
    # the classes descended from it

    def xyzzy
      # model specific functionality
    end

    def hidden
      # model specific functionality that's not delegatable
    end

    module Delegator
      AssetDelegates = [ :prop1, :prop1=, :xyzzy ]
      def self.included(other)
        other.module_eval <<-END
          # we use belongs_to rather than has_one because the foreign 
key
          # is in this table
          belongs_to :asset

          alias :__asset_delegator_old_mm :method_missing
          def method_missing(method, *args, &block)
            if AssetDelegates.include?(method)
              self.asset.send(method, *args, &block)
            else
              __asset_delegator_old_mm(method, *args, &block)
            end
          end
        END
      end
    end
  end

  class SomeAsset < ActiveRecord::Base
    include Asset::Delegator

    def xyzzy
      # override Asset's implementation of xyzzy, feels just like
      # inheritance, except that if we want to call up to the "parent"
      # version, we need to use asset.xyzzy rather than super
    end
  end

  class AnotherAsset < ActiveRecord::Base
    include Asset::Delegator

    # make the hidden method, which wasn't explicitly delegated, 
available
    # from this "subclass"
    def hidden
      asset.hidden
    end
  end

Jacob Fugal
Posted by Peter Ertl (Guest)
on 2006-06-28 20:14
(Received via mailing list)
Read about polymorphic associations...

here's an example
http://www.fromdelhi.com/2006/06/15/polymorphic-association-in-rails/

-------- Original-Nachricht --------
Datum: Thu, 29 Jun 2006 02:11:05 +0900
Von: Marcus Andersson <m-lists@bristav.se>
An: ruby-talk@ruby-lang.org
Betreff: Re: How to model Hibernate multi-table inheritance with 
ActiveRecord?
Posted by Peter Ertl (Guest)
on 2006-06-28 20:22
(Received via mailing list)
delegation is powerful, no question...

> but if SomeAsset and AnotherAsset instances each hold a delegate Asset

why not simply use inheritance?

it's not that bad :-)

class SomeAsset < Asset
class AnotherAsset < Asset


waaay tooo much java engineers here :-)


-------- Original-Nachricht --------
Datum: Thu, 29 Jun 2006 02:17:24 +0900
Von: Jacob Fugal <lukfugl@gmail.com>
An: ruby-talk@ruby-lang.org
Betreff: Re: How to model Hibernate multi-table inheritance with 
ActiveRecord?
Posted by Jacob Fugal (Guest)
on 2006-06-28 20:30
(Received via mailing list)
On 6/28/06, Peter Ertl <pertl@gmx.org> wrote:
> delegation is powerful, no question...
>
> > but if SomeAsset and AnotherAsset instances each hold a delegate Asset
>
> why not simply use inheritance?
>
> it's not that bad :-)

Woah... no one said inheritance was bad. Indeed, inheritance is what
is desired here. But due to constraints from both ActiveRecord and the
existing DB schema, we're just trying to find any way that works. And
inheritance won't work here. Particularly because a model class can
only map to one table. If you subclass an ActiveRecord model, the
subclass is assumed to live in the same table (Single Table
Inheritance). ActiveRecord doesn't currently support Multiple Table
Inheritance (as far as I know). Thus inheritance, sadly, doesn't work
for us here.

> waaay tooo much java engineers here :-)

Nope. Not the case. :) Just trying to solve a problem under 
constraint...

Jacob Fugal
Posted by Pete (Guest)
on 2006-06-28 22:08
(Received via mailing list)
> Particularly because a model class can only map to one table.

Why should that be the case?

Did you try ActiveRecord::Base.set_table_name ?

class SomeAsset < Asset
    set_table_name 'some_asset'
end


Another approach would be to include the Asset base functionality using
'include'

class SomeAsset < ActiveRecord::Base
    include Asset
end

It should be more efficient than delegating methods...


Jacob Fugal schrieb:
Posted by Marcus Andersson (marcan)
on 2006-06-28 22:25
(Received via mailing list)
Pete skrev:
> > Particularly because a model class can only map to one table.
>
> Why should that be the case?
>
> Did you try ActiveRecord::Base.set_table_name ?
>
> class SomeAsset < Asset
>    set_table_name 'some_asset'
> end
>
But still, even if you override the default name of the table you map to
you can only map to one table.
>
> Another approach would be to include the Asset base functionality 
> using 'include'
>
> class SomeAsset < ActiveRecord::Base
>    include Asset
> end
>
> It should be more efficient than delegating methods...
Huh? I can only include modules as far as I know. And I can't map a
module to a table using ActiveRecord so I think that would be hard. No?

/Marcus
Posted by Jacob Fugal (Guest)
on 2006-06-28 22:38
(Received via mailing list)
On 6/28/06, Pete <pertl@gmx.org> wrote:
>  > Particularly because a model class can only map to one table.
>
> Why should that be the case?
>
> Did you try ActiveRecord::Base.set_table_name ?
>
> class SomeAsset < Asset
>     set_table_name 'some_asset'
> end

As Marcus mentioned, this lets you *change* the table for the model,
but not *aggregate* multiple tables for the same model. The case
Marcus proposed in his original post requires an aggregation of
multiple tables to define one model.

> Another approach would be to include the Asset base functionality using
> 'include'
>
> class SomeAsset < ActiveRecord::Base
>     include Asset
> end
>
> It should be more efficient than delegating methods...

That's actually pretty much what I'm trying to achieve with my
Asset::Delegator. The reason I can't just include Asset is 1) only
modules can be included, as Marcus pointed out and 2) importing (or
delegating) *all* of the methods from Asset can conflict with the
already existing method_missing functionality for SomeAsset provided
by ActiveRecord. So I threw together a quick example of a module that
*can* be mixed in that feels like you're mixing in Asset itself, but
with a limited method set. True, the number of methods that need to be
enumerated in Asset::Delegator::AssetDelegates will probably be high,
depending on usage, but I can't see anyway around that without
polluting method_missing.

Jacob Fugal
Posted by Marcus Andersson (marcan)
on 2006-06-28 22:54
(Received via mailing list)
Hello Jacob,

I sort of like this solution but I have one problem. Querying. Say I
want to query the complete set of Assets and I don't care what
underlying [other]Asset they have. Then I have to do:

assets = Asset.find(:all, :condition => "somecondition")

But, I always need to get the delegate from the assets in order to work
with them. Thus the above expression changes to:

assets = Asset.find(:all, :condition =>
"somecondition").map(&:delegate_asset)

Now I have the correct asset instances to work with. The problem is that
I have to do that extra map() all over the place. 1) It violates DRY and
2) someone will forget it

Is there a solution to this (apart from decreing project wide that you
may never query Asset outside of Asset itself and thereby encapsulate
all queries)?

/Marcus
Posted by Jacob Fugal (Guest)
on 2006-06-28 23:11
(Received via mailing list)
On 6/28/06, Marcus Andersson <m-lists@bristav.se> wrote:
> I sort of like this solution but I have one problem. Querying.

This is a good point. I actually noticed it in your earlier reply to
Alex, but couldn't think of anything to add. As far as I can tell, the
situation is indeed as you tell it. :(

Anyone else have any ideas on this?

Jacob Fugal
Posted by Marcus Andersson (marcan)
on 2006-06-28 23:21
(Received via mailing list)
Peter Krantz skrev:
> On 6/28/06, Marcus Andersson <m-lists@bristav.se> wrote:
>>
>> I have this "old" database mainly modeled for Hibernate...
>> [snip]
> 
> Depending on how your model looks, one idea would be to use RBatis
> which is a more flexible O/R mapper. If SQL doesn't scare you that is.
> See http://jutopia.tirsen.com/articles/2006/05/23 for more details.
> 
Heh, I'm not scared of SQL. I've actually used iBatis (I used to be a
hardcore java junkie...) in the past so I'm quite familiar with its
concepts. It's actually possible to use pure SQL queries with
ActiveRecord as well.

The problem is that I don't know how to solve this problem using
straight SQL either without making the lives of the Asset clients a lot
harder.

/Marcus
Posted by Marcus Andersson (marcan)
on 2006-06-28 23:28
(Received via mailing list)
So far I have two solutions:

1) implement method_missing in Asset and delegate to [other]Asset where
Asset cannot handle the requested method
2) implement method_missing in [other]Asset and delegate back to Asset
where [other]Asset cannot handle the request (or a variant thereof)

1) gives easy querying (I can just query Asset for all objects) but I
cannot override methods from Asset in [other]Asset. Not really good.
This solution could be good if I could somehow make Asset ask
[other]Asset if they have the requested method before calling the method
in itself instead? Sort of like a default before handler on every 
method.
2) In order for this to work I always need to work on instances of
[other]Asset. That in turns mean that I either have to make the query on
[other]Asset class to get instances of [other]Asset or make the query on
Asset and then start off by getting the [other]Asset instance from it
and work with that (ie using Asset.find(:all).map(&:delegate_asset)).
Neither of them feels really polymorphic. A solution here would be if
one could bend ActiveRecord to return [other]Asset instead of Asset
instances when querying the Asset class. I suppose this would be the
solution to multi table inheritance though if I could solve it...

What I'm after is a solution to make it transparet for the clients of
the Asset class (and associates). I don't care how much/ugly code I have
to hide in Asset to just make it transparent to the clients (since I
have control over Asset, not over the clients).

Have I missed anything? 2) sounds easy and good but I can't grip the
querying situation (beyond what I described above). Have I missed
something there?

Thanks for the answers.

/Marcus
Please log in before posting. Registration is free and takes only a minute.
Existing account (Switch to SSL-encrypted connection)
NEW: Do you have a Google/GoogleMail or Yahoo account? No registration required!
Log in with Google account | Log in with Yahoo account
No account? Register here.