I have two tables, challenges
and challenge_steps
. Both tables need to have relation between them, I need to be able to reference a Step
with a Challenge
and the inverse relationship.
A challenge
can have multiple steps
but ONLY ONE current_step
.
Schema:
Challenge
:
t.string "name"
t.string "subtitle"
t.text "brief", null: false
t.integer "min_team_size", default: 2, null: false
t.integer "max_team_size", default: 5, null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
Challenge::Step
:
t.integer "challenge_id"
t.string "name"
t.text "description"
t.datetime "start_at"
t.datetime "end_at"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
To do this I can think of three solutions, but none of them are satisfying:
Challenge
Model:
has_many :steps, inverse_of: :challenge, dependent: :destroy
belongs_to :current_step, class_name: Challenge::Step
Challenge::Step
:
belongs_to :challenge
has_one :challenge_relation, class_name: Challenge,
foreign_key: :current_step_id, dependent: :restrict_with_error
As you can see in my Challenge::Step
model I have a belongs_to(:challenge)
and the Rails documentation reads:
For example, it makes more sense to say that a supplier owns an account than that an account owns a supplier.
So the behavior is OK, but the code looks odd.
Create a table which contains challenge_id
and step_id
. Which will reference each challenge
and its current_step
This one is good but it mean we need the read another table to get the needed info.
add in the Challenge
model:
has_many :steps, inverse_of: :challenge, dependent: :destroy do
def current_step
proxy_association.owner.steps.where(current_step: true).first
end
end
It returns a collection and the schema doesn't respect the real relation between a Challenge and his step.
What would most efficient and elegant? Could you think of a solution which would have none of these drawbacks ?
First of all, why is Challenge::Step
a subclass of Challenge
?
Surely you'd want it to be Step
on its own? For the purposes of clarity, I will refer to it just as Step
.
--
Here's what I'd do:
#app/models/challenge.rb
class Challenge < ActiveRecord::Base
has_many :steps
def current
steps.where(current: true).order(current: :desc).first
end
end
#app/models/step.rb
class Step < ActiveRecord::Base
# columns id | challenge_id | current (datetime) | etc...
belongs_to :challenge
end
This will give you the ability to call:
@challenge = Challenge.find params[:id]
# @challenge.steps = collection of steps
# @challenge.current_step = latest current step
The idea being that you could save your current_step
attribute as a date in the Step
model. This will have the added benefit of giving you the ability to see the historical record of when each step was "current".
--
An alternative would be to make a current
column in the Challenge
model:
#app/models/challenge.rb
class Challenge < ActiveRecord::Base
# columns id | name | current | etc
has_many :steps
def current_step
steps.find current
end
end
#app/models/step.rb
class Step < ActiveRecord::Base
#columns id | challenge_id | name | etc
belongs_to :challenge
end
This will allow you to call the following:
@challenge = Challenge.find params[:id]
# @challenge.steps = collection of steps
# @challenge.current_step = single instance of step
--
Your third solution is by far most elegant, but it assumes the structure you have implemented being correct.
I think you don't have the correct setup to handle the current_step
attribute; you either need a way to distinguish it in the Step
model or the Challenge
model.