Dynamically Defined has_many with odd behavior
Here's a quirk that's confounding me:
Data Model:
class Athlete < ApplicationRecord
has_many :stats
end
class Stat < ApplicationRecord
belongs_to :athlete
belongs_to :position, polymorphic: true
end
# then I have a long list of positions, namespaced like so:
class Position::Quarterback < ApplicationRecord
has_many :stats
has_many :athletes, through: :stats
end
class Position::RunningBack < ApplicationRecord
has_many :stats
has_many :athletes, through: :stats
end
I've got a big list of all the other positions elsewhere in the application.
Position.full_position_names #=> ["Quarterback", "RunningBack", ...]
Figured it would be much nicer to define all the has_many relationships off that list rather than type them all out by hand, so when the list changes, so does the defined relationship.
class Athlete < ApplicationRecord
Position.full_position_names.each do |position|
self.send(:has_many, # => dynamically calling has_many with position names so we can change things easier
"#{position.to_s.underscore}_stats".to_sym, # => position name Quarterback will produce association quarterback_stats
-> { order(season: :asc)}, # => ordered by season with lowest year first
through: :stats, # => look at stats table
source: :position, # => since stat->position is polymorphic, we want it to look at :position_type column on stats table
source_type: "Position::#{position.to_s}") # => with a :position_type of Position::Quarterback
end
end
So far, so good. Now when I call quarterback_stats, it works:
irb(main):007:0> Athlete.first.quarterback_stats
Athlete Load (0.7ms) SELECT "athletes".* FROM "athletes" ORDER BY "athletes"."id" ASC LIMIT $1 [["LIMIT", 1]]
Position::Quarterback Load (0.8ms) SELECT "quarterbacks".* FROM "quarterbacks" INNER JOIN "stats" ON "quarterbacks"."id" = "stats"."position_id" WHERE "stats"."athlete_id" = $1 AND "stats"."position_type" = $2 ORDER BY "quarterbacks"."season" ASC [["athlete_id", 212080005], ["position_type", "Position::Quarterback"]]
=> #<ActiveRecord::Associations::CollectionProxy [#<Position::Quarterback id: 801653172, season: 2013, passing_yards: 180, passing_touchdowns: 8, rushing_yards: 80, rushing_touchdowns: 2, created_at: "2016-05-10 16:20:06", updated_at: "2016-05-10 16:20:06">, #<Position::Quarterback id: 833410073, season: 2014, passing_yards: 180, passing_touchdowns: 8, rushing_yards: 80, rushing_touchdowns: 2, created_at: "2016-05-10 16:20:06", updated_at: "2016-05-10 16:20:06">, #<Position::Quarterback id: 111928452, season: 2015, passing_yards: 180, passing_touchdowns: 8, rushing_yards: 80, rushing_touchdowns: 2, created_at: "2016-05-10 16:20:06", updated_at: "2016-05-10 16:20:06">, #<Position::Quarterback id: 530756924, season: 2016, passing_yards: 180, passing_touchdowns: 8, rushing_yards: 80, rushing_touchdowns: 2, created_at: "2016-05-10 16:20:06", updated_at: "2016-05-10 16:20:06">]>
But when I go and call running_back_stats...It doesn't work :(
irb(main):008:0> Athlete.first.running_back_stats
Athlete Load (0.6ms) SELECT "athletes".* FROM "athletes" ORDER BY "athletes"."id" ASC LIMIT $1 [["LIMIT", 1]]
Position::RunningBack Load (0.7ms) SELECT "running_backs".* FROM "running_backs" INNER JOIN "stats" ON "running_backs"."id" = "stats"."position_id" WHERE "stats"."athlete_id" = $1 AND "stats"."position_type" = $2 ORDER BY "running_backs"."season" ASC [["athlete_id", 212080005], ["position_type", "Position::RunningBack"]]
=> #<ActiveRecord::Associations::CollectionProxy []>
But when I do the same query manually....
irb(main):009:0> Athlete.first.stats.where(position_type: "Position::RunningBack")
Athlete Load (0.5ms) SELECT "athletes".* FROM "athletes" ORDER BY "athletes"."id" ASC LIMIT $1 [["LIMIT", 1]]
Stat Load (0.5ms) SELECT "stats".* FROM "stats" WHERE "stats"."athlete_id" = $1 AND "stats"."position_type" = $2 [["athlete_id", 212080005], ["position_type", "Position::RunningBack"]]
=> #<ActiveRecord::AssociationRelation [#<Stat id: 419195171, athlete_id: 212080005, position_type: "Position::RunningBack", position_id: 505436969, created_at: "2016-05-10 16:20:06", updated_at: "2016-05-10 16:20:06">, #<Stat id: 110689410, athlete_id: 212080005, position_type: "Position::RunningBack", position_id: 4509324, created_at: "2016-05-10 16:20:06", updated_at: "2016-05-10 16:20:06">, #<Stat id: 832556053, athlete_id: 212080005, position_type: "Position::RunningBack", position_id: 927202847, created_at: "2016-05-10 16:20:06", updated_at: "2016-05-10 16:20:06">, #<Stat id: 680959409, athlete_id: 212080005, position_type: "Position::RunningBack", position_id: 776646567, created_at: "2016-05-10 16:20:06", updated_at: "2016-05-10 16:20:06">]>
I get back all the relevant stats that point to the existing positions.
Any idea what could be going on here?
No dice! I think Rails will automatically take the modelname_type stored on whatever polymorphic model and get the table/instantiate based off that, else I wouldn't have been able to successfully query the quarterbacks.
But if you have any other ideas I would love to hear em! I'm stuck. :(
The queries that aren't working are because the positions don't exist. I just assumed they had to in order for the join table to point at them...not sure what happened yet!
my feels: :D .... D: ... :D ... :'(
Retrospecting, I should have tried to reproduce this independently of the app much, much sooner in the process. like after 15 minutes soon. Would've realized it right then.