Search code examples
ruby-on-railsparametersmodelnested-attributes

Displaying information from unrelated nested models


I'm building a Rails application with two models, both with a further two nested models:

Contacts -> Trackers -> Emails

and

Goals -> Stages -> Templates

I want to display information from a specific Goal on the show page of a particular Tracker but I'm struggling to get the right data to appear.

I have a field for goal_id in my Tracker parameters and I've added this line to my Tracker controller but I think I’m doing it wrong:

@goals = Goal.find(params[:id])

I'm sure it's going to be a really obvious mistake but I'm so new to Rails that I just can't seem to get it right and I'd love any suggestions you might have. Thanks in advance!

Model for Tracker:

class Tracker < ApplicationRecord
  belongs_to :contact
  has_one :goal
  has_many :emails, dependent: :destroy
end

Model for Goal:

class Goal < ApplicationRecord
  has_many :stages , dependent: :destroy
  has_many :trackers
end

Controller for Tracker:

class TrackersController < ApplicationController
  before_action :get_contact
  before_action :set_tracker, only: %i[ show edit update destroy ]

  # GET /trackers or /trackers.json
  def index
    @trackers = @contact.trackers
  end

  # GET /trackers/1 or /trackers/1.json
  def show
    @goals = Goal.find(params[:id])
  end

  # GET /trackers/new
  def new
    @tracker = @contact.trackers.build
  end

  # GET /trackers/1/edit
  def edit
  end

  # POST /trackers or /trackers.json
  def create
    @tracker = @contact.trackers.build(tracker_params)

    respond_to do |format|
      if @tracker.save
        format.html { redirect_to contact_trackers_path(@contact), notice: "Tracker was successfully created." }
        format.json { render :show, status: :created, location: @tracker }
      else
        format.html { render :new, status: :unprocessable_entity }
        format.json { render json: @tracker.errors, status: :unprocessable_entity }
      end
    end
  end

  # PATCH/PUT /trackers/1 or /trackers/1.json
  def update
    respond_to do |format|
      if @tracker.update(tracker_params)
        format.html { redirect_to contact_tracker_path(@contact), notice: "Tracker was successfully updated." }
        format.json { render :show, status: :ok, location: @tracker }
      else
        format.html { render :edit, status: :unprocessable_entity }
        format.json { render json: @tracker.errors, status: :unprocessable_entity }
      end
    end
  end

  # DELETE /trackers/1 or /trackers/1.json
  def destroy
    @tracker.destroy

    respond_to do |format|
      format.html { redirect_to contact_trackers_path(@contact), notice: "Tracker was successfully destroyed." }
      format.json { head :no_content }
    end
  end

  private
    # Use callbacks to share common setup or constraints between actions.
    def get_contact
      @contact = Contact.find(params[:contact_id])
    end


    def set_tracker
      @tracker = @contact.trackers.find(params[:id])
    end

    # Only allow a list of trusted parameters through.
    def tracker_params
      params.require(:tracker).permit(:name, :contact_id, :goal_id, :stage_id, :status, :note)
    end
end

Show page view for Tracker:

  <div class="card">
    <h3>Goal</h3>
    <%= @goals.name %>

  </div>

Solution

  • in your TrackersController you have:

      # GET /trackers/1 or /trackers/1.json
      def show
        @goals = Goal.find(params[:id])
      end
    

    It looks like you're taking a Tracker ID and using that ID as the ID of a Goal.

    It's unlikely that (using 709 as an example) Tracker 709, is associated with Goal 709, so this is very likely an error. You do have an association between the models though, so you could use that to find the goal associated with the Tracker.

    @goal = @tracker.goal
    

    Note also in the above I'm using @goal not @goals since there's only one associated. You'll need to update your view to also use @goal. Alternatively in your view you could just access the goal directly via @tracker.goal; then you don't need to set @goal at all.


    Potential model issues: It looks like Tracker has_one goal and Goal has_many trackers. This looks like you wanted a normal n-to-1 relationship. So you probably want to change that has_one to a belongs_to instead. (It doesn't imply ownership.)

    As is you're telling Rails:

    1. Goal has_many trackers -> the trackers table has a goal_id column that defines the relationship.
    2. Tracker has_one goal -> the goals table has a tracker_id column that defines the relationship.

    I don't know what migration(s) you've run, but you probably only want one of these two columns (goal_id and tracker_id). You should take a look at your schema and fix this. (You may also want to try using the annotate_models gem so you can easily see the columns in your tables.)