How to: Single Table Inheritance in DataMapper?
I would recommend this approach:
module DownloadableResource
def self.included base
base.class_eval do
include DataMapper::Resource
property :created_at, DateTime
property :modified_at, DateTime
property :active, base::Boolean, default: true
property :position, Integer
property :title, String, required: true
property :url, base::URI, required: true
property :description, base::Text, required: true
property :type, base::Discriminator
end
end
end
class Book
include DownloadableResource
property :id, Serial
# other properties
end
class Download
include DownloadableResource
property :id, Serial
# other properties
end
DataMapper - Single Table Inheritance
If you look at the SQL that's being generated it gives you a clue as to what's going on (if you don't know, you can do this with DataMapper::Logger.new(STDOUT, :debug)
before your call to DataMapper::setup
).
Person.all
simply generates:
SELECT "id", "type", "name", "age" FROM "people" ORDER BY "id"
as you would expect.Male.all
generates:
SELECT "id", "type", "name", "age" FROM "people"
WHERE "type" IN ('Male', 'Father', 'Son') ORDER BY "id"
and Person.all(:type => Male)
generates:SELECT "id", "type", "name", "age" FROM "people"
WHERE "type" = 'Male' ORDER BY "id"
So when you use Male.all
Datamapper knows to create an SQL IN
clause containing the names of all the appropriate classes, but when you use Person.all(:type => Male)
uses just the type you specify and none of the subclasses.A similar direct comparison must be happening when you query a collection rather than the database with @people.all(:type => Male)
.
In order to correctly get all subclasses of a type in a query you can use the descendants
method.
People.all(:type => Male.descendants)
generates this SQL:
SELECT "id", "type", "name", "age" FROM "people"
WHERE "type" IN ('Father', 'Son') ORDER BY "id"
In this case this will return what you want, but note that the IN
clause doesn't contain Male
, it's only the descendants of the model, not including the parent of the subtree in question.To get the 'top' class as well, you could use:
Person.all(:type => Male.descendants.dup << Male)
to add the Male
class to the IN
clause. Note the dup
is needed, otherwise you'll get stack level too deep (SystemStackError)
.This will also work on collections as expected:
@people.all(:type => Male.descendants.dup << Male)
will return all males without hitting the database (assuming @people
already contains all the people).If you do decide to use the descendants
method, note that although it isn't marked as private in the docs, it's marked @api semipublic
in the source, so look out when upgrading Datamapper. The <<
method in Male.descendants.dup << Male
is marked as private, so the warning applies even more here. I got this usage from the source to Discriminator.
Missing properties
Your other issue about not being able to get certain properties looks like it's something to do with lazy loading, but I can't work out exactly what's going on. It might be a bug.When you load all females with @women = Female.all
the SQL generated is:
SELECT "id", "type", "name", "age" FROM "people"
WHERE "type" IN ('Female', 'Mother', 'Daughter') ORDER BY "id"
so only attributes possessed by all members are fetched. The favorite_song
of mothers is then fetched the first time you reference it. The lines:puts women.first.inspect
puts women.first.favorite_song
puts women.first.inspect
give (including the SQL log showing when the missing value is fetched):#<Mother @id=4 @type=Mother @name="Wanda" @age=47 @favorite_song=<not loaded>>
~ (0.000069) SELECT "id", "type", "favorite_song" FROM "people" WHERE "id" = 4 ORDER BY "id"
Suspicious minds
#<Mother @id=4 @type=Mother @name="Wanda" @age=47 @favorite_song="Suspicious minds">
However, when I do something similar in an each
block, the query to fetch the missing value doesn't incude the favorite_song
, and the value is set to nil in the model:women.each do |w|
next unless w.respond_to? :favorite_song
puts w.inspect
puts w.favorite_song
puts w.inspect
end
gives the output (again including the SQL):#<Mother @id=4 @type=Mother @name="Wanda" @age=47 @favorite_song=<not loaded>>
~ (0.000052) SELECT "id", "type" FROM "people" WHERE "type" IN ('Female', 'Mother', 'Daughter') ORDER BY "id"
#<Mother @id=4 @type=Mother @name="Wanda" @age=47 @favorite_song=nil>
I don't know if I'm missing something here, or if this is indeed a bug. One-to-one DataMapper association
There are different ways you could do this. Here's one option:
class User
include DataMapper::Resource
property :id, Serial
# Other properties...
has 1, :referee, :required => false
has 1, :player, :required => false
end
class Referee
include DataMapper::Resource
# DON'T include "property :id, Serial" here
# Other properties...
belongs_to :user, :key => true
end
class Player
include DataMapper::Resource
# DON'T include "property :id, Serial" here
# Other properties...
belongs_to :user, :key => true
end
Act on the referee/player models like:u = User.create(...)
u.referee = Referee.create(...)
u.player = Player.create(...)
u.player.kick_ball() # or whatever you want to call
u.player.homeruns
u.referee.flag_play() # or whatever.
See if this works. I haven't actually tested it but it should be good. DataMapper associations/validation causing save failure
Okay, so I derped on inheritance, and should have read more of the documentation. What was happening was I missed the property :type, Discriminator
needed when using subclasses. So even though my one subclass didn't need a device associated with it, the other did, so that's what was triggering the error.
My working model looks like this: http://www.pastie.org/2564668
DataMapper to migrate data from one table to another.
I don't think that's the intention of dm-migrations. I believe the easiest way would be something like this:
DataMapper.setup(:default, db1_config)
DataMapper.setup(:new, db2_config)
class Foo
include DataMapper::Resource
property :id, Serial
property :name, String
...
end
DataMapper.finalize
Foo.each do |foo|
DataMapper.repository(:new) do
# It may not let you set the "id" attribute here...
Foo.create(foo.attributes)
end
end
EditIn hindsight, I'm not sure if you were asking how to to copy table structure as to opposed to table data. This is obviously copying table data.
Single Table Inheritance in Sequel::Model (Ruby ORM)
You want the single_table_inheritance plugin:
class Provider < Sequel::Model
plugin :single_table_inheritance, :provider_type
end
class Center < Provider
end
class Sponsor < Provider
end
This will work, but only if the provider_type column matches exactly "Center" or "Sponsor". If not, you might need to add a :model_map option to the plugin call. The documentation on this plugin is located at http://sequel.jeremyevans.net/rdoc-plugins/classes/Sequel/Plugins/SingleTableInheritance.html Datamapper 'belongs_to / has n' ignoring :required = false
There used to be a bug in old DM which was causing this behavior. I'd strongly suggest porting this app to recent Rails & DM.
Ruby Datamapper will not insert serial data
Thanks to Zoltan for putting me on the right track. I added .chomp to sp.gets to remove the new line that sp.gets does. This allowed the database insert to occur.
Having done that, I still require the new line for the printf and file insert so will need to alter some code. Not expecting too many issues with this.
Related Topics
Require Tree in Asset Pipeline
How Do Erlang Actors Differ from Oop Objects
Private Messages with Faye and Rails
Reading Files in a Zip Archive, Without Unzipping The Archive
What Is 'stringify_Keys' in Rails and How to Solve It When This Error Comes
Using Rails with Paperclip and Swfupload
How to Change "3 Errors Prohibited This Foobar from Being Saved" Validation Message in Rails
How to Deploy a Test App on Dreamhost Rails 3.0.4
More Ruby Way of Doing Project Euler #2
Sql Like Operator in Ruby on Rails
What Does Bundle Install -Without Production Do
Fastest Way to Skip Lines While Parsing Files in Ruby
Rails Validating Search Params
Sinatra Not Persisting Session with Redirect on Chrome
Running "Bundle Install" Fails and Asks Me to Run "Bundle Install"
Using Multiple Yields to Insert Content
Git: Forcing Tests Before Pushing to Local or Remote Master
Foreign Key (Class_Id) Not Populating in Belongs_To Association