Search code examples
ruby-on-railsformsactiverecordassociationshas-many-through

Rails bi-directional / self join, populate form and show page


Problem: I have a model Exercise, this model can have exercises that are variations of each other. So a self join table I thought.

Like a join table of Foo, Boo, Coo, where Foo and Boo are models link by the joining table Coo allows for @foo.boo and @boo.foo

So I looked and read these posts describing bi and uni directional relation ships 1, 2. So the table stores the data correctly but now I'm have trouble on creating the correct query.

EDIT:

After reading more on how a join table works and naming conventions I see that the methods are working like they should. My question should be about how do I not cause if statements in the show and form page.

The way that it is set, if I was editing object a that had a relation to object b it would populate via the @a.variations. However, if I go to object b I have to get the relation via @b.exercises which does not seem correct but works. Further more, the same idea would have to be repeated on the show page. How does one go about making this a uniform "call" i.e. @a.variations and @b.variations to populate the forms and the show? Is it even possible?

UPDATE/Clarification: To question below regarding variations.

If there are many objects that are referenced, i.e. A is a variation of B and C is a variation of B then A would also be a variation of C. So a query of @a.variations = [B,C]; @b.variations = [A,C]; @c.variations = [A,B]. Hopefully this clarifies the question and my thinking behind this relation/query.

(extra info)- The reason being some people wont be able to squat so they will have to start with a different exercise that target the same muscles and build there way up to the squat. Or you could have been injured or on a recovery day and to target the muscles with a less complex movement.

Schema migration, Models, Controllers, Forms:

create_table :exercise_variation_relations do |t|
  t.references :exercise, foreign_key: true, null: false, index: true
  t.references :exercise_variation, foreign_key: { to_table: :exercises }, null: false, index: true 
end

class ExerciseVariationRelation < ApplicationRecord
        belongs_to :exercise, foreign_key: "exercise_id", class_name: "Exercise" 
        belongs_to :exercise_variation, foreign_key: "exercise_variation_id", class_name: "Exercise" 
    end

class Exercise < ApplicationRecord
**other code
        has_many :exercise_drills, foreign_key: :exercise_variation_id, class_name: "ExerciseVariationRelation"
        has_many :exercises, through: :exercise_drills, source: :exercise
    
     
has_many :variation_exercises, foreign_key: :exercise_id, class_name: "ExerciseVariationRelation"
has_many :variations, through: :variation_exercises, source: :exercise_variation

    validates_associated: :variation_exercises
    accepts_nested_attributes_for :variation_exercises

    ** other code
    end

Contoller 
params.fetch(:exercise, {}).permit( **other params
                                   variation_exercises_attributes: [:id, :exercise_variation_id, :_destroy], )

Form
   <%= f.fields_for :variation_exercises, ExerciseVariationRelation.new, child_index: 'NEW_RECORD' do |e| %>
    <%= render "form_variation", form: e %>
   <% end %>

Form_variation
<%= content_tag :div, class: "nested-fields" do %>
<%= form.collection_select(:exercise_variation_id, Exercise.all, :id, :name, {}, {class: 'form-control'}) %>
<% end %>

I understand how join tables work but this self join/relation is confusing still.


Solution

  • i think your model (at least how it is presented in your question) needs some clarification :) for instance:

    • do these relations act like a transitive network? if A is a variation of B and B is a variation of C, does that make A a variation of C?
    • if A is a variation of B, does that make B a variation of A?

    in the simplest case, this should work i think:

    create_table :exercise_variations do |t|
        t.references :exercise, foreign_key: true, null: false, index: true
        t.references :variation, foreign_key: { to_table: :exercises }, null: false, index: true 
    end
    
    class ExerciseVariation < ApplicationRecord
        belongs_to :exercise, foreign_key: "exercise_id"
        belongs_to :variation, foreign_key: "variation_id", class_name: "Exercise" 
    end
    
    class Exercise < ApplicationRecord
        has_many :exercise_variations, class_name: "ExerciseVariation"
        has_many :variations, through: :exercise_variations
        
        has_many :variation_exercises, foreign_key: :variation_id, class_name: "ExerciseVariation"
        has_many :variations, through: :variation_exercises
    end