Search code examples
rubyruby-datamappersqlite3-ruby

can I join two join resources together?


The way I have implemented it, it would appear not.

In data_mapper terms - I have a join resource Entry, that joins Driver, Team and Season

I have a join resource Race that joins Track and Season

I have a join resource Placing that joins Entry and Race.

When I try to do something like "Show me all of the races that a particular entry has taken part in with something like

@entry_a.races.all

I get this error

DataObjects::SyntaxError: no such column: INNER (code: 1, sql state: , query: SELECT "races"."track_id", "races"."season _id", "races"."race_no" FROM "races" INNER JOIN "placings" ON INNER JOIN "entries" ON "placings"."entry_team_id" = "ent ries"."team_id" AND "placings"."entry_driver_id" = "entries"."driver_id" WHERE ("placings"."entry_team_id" = 'FER' AND " placings"."entry_driver_id" = 'MAS') GROUP BY "races"."track_id", "races"."season_id", "races"."race_no" ORDER BY "races "."track_id", "races"."season_id", uri: sqlite3:C/devProjects/formula1../spec/resources/test.db?scheme=sqlite3&user=&pas sword=&host=C&port=&query=&fragment=&adapter=sqlite3&path=/devProjects/formula1../spec/resources/test.db)

Its pretty easy to see what has happened here, the query wrapped by the .all method isn't expecting a join on join.

I managed to workaround this by writing some custom sql. While not ideal, it does do the job. It doesn't seem to be the ruby way though. Maybe my db schema sucks?

Here are my models (sorry about the code dump. I have gotten burned by a fellow SO who decided to downvote me because I hadn't quoted complete code)

require "rubygems"
require "sqlite3"
require "data_mapper"
require "bigdecimal"

#note that Dir.pwd refers to the location of the file that calls
if ENV["run_mode"] == "prod"
    DataMapper::setup(:default, "sqlite3://#{Dir.pwd}/data/prod.db")
else
    DataMapper::setup(:default, "sqlite3://#{Dir.pwd}../spec/resources/test.db")
end

class Entry

    include DataMapper::Resource

    belongs_to :team,   :key => true
    belongs_to :driver, :key => true
    belongs_to :season, :key => true

    has n, :placings
    has n, :races, :through => :placings

    def find_races

        return repository.adapter.select('SELECT races.track_id, races.season_id, races.race_no FROM races INNER JOIN placings ON races.track_id = placings.race_track_id INNER JOIN entries ON placings.entry_driver_id = entries.driver_id WHERE (entries.team_id = ? AND entries.driver_id = ?)', self[:team_id], self[:driver_id])

    end

end

class Track

    include DataMapper::Resource

    property :id, String, :key => true

    has n, :races

end

class Race

    include DataMapper::Resource

    property :race_no, Integer

    belongs_to :track,  :key => true
    belongs_to :season, :key => true

    has n, :placings
    has n, :entries, :through => :placings


end

class Placing

    include DataMapper::Resource

    property :id, Serial #put this in because dm was complaining that foreign keys from entry object were not unique
    belongs_to :race    
    belongs_to :entry

end

class Season

    include DataMapper::Resource
    property :id, Integer, :key => true

    has n, :races
    has n, :entries

end

class Driver

    include DataMapper::Resource
    property :id, String, :key => true
    property :team, String
    property :ptd, Integer
    property :races, Integer
    property :grid, Object
    property :race, Object
    property :cumulativePoints, Object #the number of points accumulated at a particular point in the season
    property :hcScore, Integer
    property :domScore, Integer
    property :nation, String
    property :wins, Integer
    property :podiums, Integer

    has n, :entries
    has n, :teams, :through => :entries

end

class Team
    include DataMapper::Resource
    property :id, String, :key =>  true
    property :name, String
    property :ptd, Integer
    property :domScore, Integer
    property :nation, String

    has n, :entries
    has n, :drivers, :through => :entries

    def initialize(team_id)
        self[:id] = team_id
        self.save!
    end

    def add_driver(driver_id)
        @driver = Driver.create(:id => driver_id)
        Entry.create(:driver => (Driver.get driver_id), :team => self, :season =>  Season.last)
        return @driver
    end

end

class Nation
    include DataMapper::Resource
    property :id, String, :key => true
    property :ptd, Integer
    # could possibly have the drivers, teams and engines here as well
end

if ENV["run_mode"] == "test"
    DataMapper.finalize.auto_migrate!
else
    DataMapper.finalize.auto_upgrade!
end

Solution

  • After some testing, I think that the error you're seeing is related to having composite keys; try changing for a single Serial key for Race. I think it's a bug in DM.

    I had the same error appear when trying to do Driver.first.races with composite keys, but with just a Serial key it worked:

    $KCODE='u'
    
    require 'rubygems'
    require 'dm-core'
    require 'dm-migrations'
    
    DataMapper::Logger.new($stdout, :debug)
    DataMapper.setup(:default, "sqlite::memory:")
    
    class Entry
      include DataMapper::Resource
      #property :id, Serial # maybe also here? Although everything seems to work without
      property :grid, Integer
      property :position, Integer
      belongs_to :driver, :key => true
      belongs_to :race, :key => true
    end
    
    class Driver
      include DataMapper::Resource
      property :name, String, :key => true
      has n, :entries
      has n, :races, :through => :entries
      def podiums; entries(:position => (1..3)) end
      def wins; entries(:position => 1) end
    end
    
    class Race
      include DataMapper::Resource
      property :id, Serial
      belongs_to :track
      belongs_to :season
      has n, :entries
      has n, :drivers, :through => :entries
    end
    
    class Season
      include DataMapper::Resource
      property :year, Integer, :key => true
      has n, :races
    end
    
    class Track
      include DataMapper::Resource
      property :name, String, :key => true
      has n, :races
    end
    
    DataMapper.finalize.auto_migrate!
    
    Entry.create(
      :driver => Driver.new(:name => "Kimi Räikkönen"),
      :grid   => 7, :position => 1,
      :race   => Race.new(
        :track  => Track.new(:name => "Albert Park"),
        :season => Season.new(:year => 2013)))