ruby-on-railsrubyruby-on-rails-3has-onerails-activerecord

Rails multiple Has_one relationship to same model


I am working on a Rails application and currently I have 2 models - Subjects and Lessons. A Subject has 3 different types of lessons - Lecture, Tutorial and Laboratory. I modelled such that there are 3 has_one to the Lesson model.

Right now, I am trying to create a nested form for subjects and lessons but the lecture, tutorial and laboratory being saved was always the first form that was rendered. i.e. I have 3 nested forms separately for Lecture, Tutorial and Laboratory but the Lecture, Tutorial and Laboratory that was saved was always the one that was first built. In my codes the lecture was first built so the attributes for tutorial and laboratory would follow the one that I have filled in for my lecture.

I am not sure where I have went wrong or even if having multiple has_one relationship works in this case so any advice would be appreciated.

The related codes are as follows:

The subject model

class Subject < ActiveRecord::Base

  has_one :lecture, :class_name => "Lesson"
  has_one :laboratory,:class_name => "Lesson"
  has_one :tutorial, :class_name => "Lesson"

  accepts_nested_attributes_for :lecture
  accepts_nested_attributes_for :laboratory
  accepts_nested_attributes_for :tutorial 

end

The lesson model

class Lesson < ActiveRecord::Base
  belongs_to :subject
end

The Subject and lesson nested form

<%= form_for(@subject_list) do |f| %>
  <div class="field">
    <%= f.label :subject_code %><br />
    <%= f.text_field :subject_code %>
  </div>
  <div>
    <%= f.fields_for :lecture do |lecture| %>
      <%= render "lecture_fields", :f => lecture %>
    <% end %>
  </div>
  <div>
    <%= f.fields_for :tutorial do |tutorial| %>
      <%= render "tutorial_fields", :f => tutorial %>
    <% end %>
  </div>
  <div>
    <%= f.fields_for :laboratory do |laboratory| %>
      <%= render "laboratory_fields", :f => laboratory %>
    <% end %>
  </div>

  <div class="actions">
    <%= f.submit %>
  </div>
<% end %>

The new action in the subject controller

    def new
    @subject = Subject.new

    lecture = @subject.build_lecture
    laboratory = @subject.build_laboratory
    tutorial = @subject.build_tutorial

    respond_to do |format|
      format.html # new.html.erb
      format.json { render json: @subject }
    end
  end

I would appreciate if someone could help me out in identifying where I have went wrong. If in the case that I should not be creating such multiple relationships, I would like to have some advice on how could I actually render out 3 forms with a default field indicating the lesson type.


Solution

  • I'm not really sure if that works, but my advise is to use AR inheritance

    class Lesson < ActiveRecord::Base
    end
    
    class LectureLesson < Lesson
      belongs_to :subject
    end
    
    class LaboratyLesson < Lesson
      belongs_to :subject
    end
    
    class TutorialLesson < Lesson
      belongs_to :subject
    end
    
    class Subject
      has_one :lecture_lesson
      has_one :laboratory_lesson
      has_one :tutorial_lesson
    
      accepts_nested_attributes_for :lecture_lesson
      accepts_nested_attributes_for :laboratory_lesson
      accepts_nested_attributes_for :tutorial_lesson
    end
    

    Migration

    class LessonsAndSubjects < ActiveRecord::Migration
      def up
        remove_column :subjects, :lesson_id
    
        add_column :subjects, :lecture_lesson_id, :integer
        add_column :subjects, :laboratory_lesson_id, :integer
        add_column :subjects, :tutorial_lesson_id, :integer
    
        add_column :lessons, :type, :string
    
        add_index :subjects, :lecture_lesson_id
        add_index :subjects, :laboratory_lesson_id
        add_index :subjects, :tutorial_lesson_id
      end
    
      def down
        remove_column :subjects, :lecture_lesson_id
        remove_column :subjects, :laboratory_lesson_id
        remove_column :subjects, :tutorial_lesson_id
    
        remove_column :lessons, :type
    
        add_column :subjects, :lesson_id, :integer
      end
    end
    

    it makes more sense and it may be fix you issue with nested attributes