Chris Kenworthy
Chris Kenworthy

Reputation: 129

How to handle data with two has_many associations

I'm new to Ruby on Rails. Just getting my head around modelling relationships and how to build CRUD with them. I'd like advice or suggestions on how to fix this problem.

In my system, a support user (student) has private support sessions (like classes) with their support worker (tutor). Here's how the system will be used: a support worker will log in, select a service user they work with, and record a support session, which includes things like how long the session lasted for and where it took place. Eventually, the system will produce time-sheets that can be used to invoice for the tutor's work in these private support sessions.

I'm not quite sure how to interact with this at a controller level. So far I've built my model and scaffolds - now I'm customising them.

I've been using the official tutorial to help me get started at: https://guides.rubyonrails.org/v3.2.8/getting_started.html but I think I've gone beyond that now.

# Models
# Not entirely sure inverse_of is appropriate but several tutorials suggested it's worth putting in
class ServiceUser < ApplicationRecord
  has_many :support_sessions, inverse_of: :service_user
  has_many :support_workers, through: :support_sessions
end

class SupportWorker < ApplicationRecord
  has_many :support_sessions, inverse_of: :support_worker
  has_many :service_users, through: :support_sessions

class SupportSession < ApplicationRecord
  belongs_to :support_worker, inverse_of: :support_sessions
  belongs_to :service_user, inverse_of: :support_sessions
end

# Controller
class SupportSessionsController < ApplicationController
  # POST /support_sessions
  # POST /support_sessions.json
  def create
  # Auto-generated line:
  @support_session = SupportSession.new(support_session_params)

  # Get service_user_id from query string
  @service_user = ServiceUser.find(params[:service_user_id])

  # Lifted from RoR 'Getting started' article
  @support_session = @service_user.support_sessions.create(params[:service_user])

  respond_to do |format|
  if @support_session.save
  #format.html { redirect_to @support_session, notice: 'Support session was successfully created.' }
  format.html { redirect_to service_users_url, notice: 'Support session was successfully created.' }
  format.json { render :show, status: :created, location: @support_session }
  else
  format.html { render :new }
  format.json { render json: @support_session.errors, status: :unprocessable_entity }
  end
  end

  end
end

# Service user view/HTML form
# This is rendered as a partial on the service user's show.html.erb view
# select_support_workers() helper retrieves a list of ALL support workers in the system and the control value is the support_worker_id
<%= form_for([@service_user, @service_user.support_sessions.build]) do |f| %>
<tr>
  <td>
  <%= select_support_workers(f) %>
  </td>
  <td>
  <%= f.text_field :location %>
  </td>
  <td>
  <%= f.text_field :mode_of_delivery %>
  </td>
  <td>
  <%= f.text_field :started_at %>
  </td>
  <td>
  <%= f.text_field :ended_at %>
  </td>
  <td>
  <%= f.text_field :total_breaks %>
  </td>
  <td>
  <%= f.submit 'Save session' %>
  </td>
</tr>

<% end %>

When I submit the form (which is being rendered as a partial on the service_user view show page), I seem to be ending up in the 'else' part of the if @support_session.save logic.

Error shown in my web browser is:

NameError in SupportSessions#create
Showing /Users/chris/git-local/timesheets/app/views/support_sessions/new.html.erb where line #5 raised:

undefined local variable or method `support_sessions_path' for #<#<Class:0x00007feb3412d028>:0x00007feb34163268>
Did you mean? @support_session
Extracted source (around line #5):

3 <%= render 'form', support_session: @support_session %>
4 
5 <%= link_to 'Back', support_sessions_path %>

Rails.root: /Users/chris/git-local/timesheets

Application Trace | Framework Trace | Full Trace
app/views/support_sessions/new.html.erb:5:in `_app_views_support_sessions_new_html_erb__485411543567831194_70324083953220'
app/controllers/support_sessions_controller.rb:46:in `block (2 levels) in create'
app/controllers/support_sessions_controller.rb:40:in `create'
Request

Parameters:

{"utf8"=>"✓",
 "authenticity_token"=>"/xd16JX3LBjA7+9DVfnHwnJziucJkaICaWg/Sd6TK2rDOrugMcZu0r5qr6SO0TfqvXzzhBqvkydMIQqXp2NtrQ==",
 "support_session"=>{"support_worker_id"=>"3", "location"=>"", "mode_of_delivery"=>"", "started_at"=>"", "ended_at"=>"", "total_breaks"=>""},
 "commit"=>"Save session",
 "service_user_id"=>"1"}

Console output

Started GET "/service_users/" for ::1 at 2019-05-31 07:58:29 +0100
   (0.5ms)  SET NAMES utf8,  @@SESSION.sql_mode = CONCAT(CONCAT(@@sql_mode, ',STRICT_ALL_TABLES'), ',NO_AUTO_VALUE_ON_ZERO'),  @@SESSION.sql_auto_is_null = 0, @@SESSION.wait_timeout = 2147483
  ↳ /Users/chris/.rvm/gems/ruby-2.6.3/gems/activerecord-5.2.3/lib/active_record/log_subscriber.rb:98
Processing by ServiceUsersController#index as HTML
Started GET "/service_users/4" for ::1 at 2019-05-31 07:58:29 +0100
  Rendering service_users/index.html.erb within layouts/application
   (0.6ms)  SET NAMES utf8,  @@SESSION.sql_mode = CONCAT(CONCAT(@@sql_mode, ',STRICT_ALL_TABLES'), ',NO_AUTO_VALUE_ON_ZERO'),  @@SESSION.sql_auto_is_null = 0, @@SESSION.wait_timeout = 2147483
  ↳ /Users/chris/.rvm/gems/ruby-2.6.3/gems/activerecord-5.2.3/lib/active_record/log_subscriber.rb:98
  ServiceUser Load (0.7ms)  SELECT `service_users`.* FROM `service_users`
Processing by ServiceUsersController#show as HTML
  ↳ app/views/service_users/index.html.erb:22
  Parameters: {"id"=>"4"}
  Rendered service_users/index.html.erb within layouts/application (7.6ms)
  ServiceUser Load (2.4ms)  SELECT  `service_users`.* FROM `service_users` WHERE `service_users`.`id` = 4 LIMIT 1
  ↳ app/controllers/service_users_controller.rb:69
  Rendering service_users/show.html.erb within layouts/application
  SupportSession Load (0.9ms)  SELECT `support_sessions`.* FROM `support_sessions` WHERE `support_sessions`.`service_user_id` = 4
  ↳ app/views/service_users/show.html.erb:18
  Rendered collection of templates [0 times] (0.0ms)
  SupportWorker Load (1.2ms)  SELECT `support_workers`.* FROM `support_workers` ORDER BY `support_workers`.`given_name` ASC
  ↳ app/helpers/support_sessions_helper.rb:15
  Rendered support_sessions/_form.html.erb (7.5ms)
  Rendered service_users/show.html.erb within layouts/application (15.6ms)
Completed 200 OK in 94ms (Views: 91.2ms | ActiveRecord: 0.7ms)


Completed 200 OK in 115ms (Views: 105.7ms | ActiveRecord: 4.5ms)


Started GET "/service_users/" for ::1 at 2019-05-31 07:58:30 +0100
Processing by ServiceUsersController#index as HTML
Started GET "/service_users/4" for ::1 at 2019-05-31 07:58:30 +0100
  Rendering service_users/index.html.erb within layouts/application
Processing by ServiceUsersController#show as HTML
  ServiceUser Load (1.5ms)  SELECT `service_users`.* FROM `service_users`
  Parameters: {"id"=>"4"}
  ↳ app/views/service_users/index.html.erb:22
  Rendered service_users/index.html.erb within layouts/application (5.9ms)
  ServiceUser Load (2.2ms)  SELECT  `service_users`.* FROM `service_users` WHERE `service_users`.`id` = 4 LIMIT 1
  ↳ app/controllers/service_users_controller.rb:69
  Rendering service_users/show.html.erb within layouts/application
  SupportSession Load (0.8ms)  SELECT `support_sessions`.* FROM `support_sessions` WHERE `support_sessions`.`service_user_id` = 4
  ↳ app/views/service_users/show.html.erb:18
  Rendered collection of templates [0 times] (0.0ms)
  SupportWorker Load (1.3ms)  SELECT `support_workers`.* FROM `support_workers` ORDER BY `support_workers`.`given_name` ASC
  ↳ app/helpers/support_sessions_helper.rb:15
  Rendered support_sessions/_form.html.erb (5.5ms)
  Rendered service_users/show.html.erb within layouts/application (12.1ms)
Completed 200 OK in 126ms (Views: 122.0ms | ActiveRecord: 1.5ms)


Completed 200 OK in 108ms (Views: 100.0ms | ActiveRecord: 4.3ms)


Started GET "/service_users/4" for ::1 at 2019-05-31 07:58:34 +0100
Started GET "/service_users/4" for ::1 at 2019-05-31 07:58:34 +0100
Processing by ServiceUsersController#show as HTML
  Parameters: {"id"=>"4"}
Processing by ServiceUsersController#show as HTML
  Parameters: {"id"=>"4"}
  ServiceUser Load (1.3ms)  SELECT  `service_users`.* FROM `service_users` WHERE `service_users`.`id` = 4 LIMIT 1
  ↳ app/controllers/service_users_controller.rb:69
  ServiceUser Load (1.5ms)  SELECT  `service_users`.* FROM `service_users` WHERE `service_users`.`id` = 4 LIMIT 1
  ↳ app/controllers/service_users_controller.rb:69
  Rendering service_users/show.html.erb within layouts/application
  Rendering service_users/show.html.erb within layouts/application
  SupportSession Load (1.5ms)  SELECT `support_sessions`.* FROM `support_sessions` WHERE `support_sessions`.`service_user_id` = 4
  ↳ app/views/service_users/show.html.erb:18
  SupportSession Load (0.7ms)  SELECT `support_sessions`.* FROM `support_sessions` WHERE `support_sessions`.`service_user_id` = 4
  Rendered collection of templates [0 times] (0.0ms)
  ↳ app/views/service_users/show.html.erb:18
  Rendered collection of templates [0 times] (0.0ms)
  SupportWorker Load (1.4ms)  SELECT `support_workers`.* FROM `support_workers` ORDER BY `support_workers`.`given_name` ASC
  ↳ app/helpers/support_sessions_helper.rb:15
  SupportWorker Load (0.7ms)  SELECT `support_workers`.* FROM `support_workers` ORDER BY `support_workers`.`given_name` ASC
  ↳ app/helpers/support_sessions_helper.rb:15
  Rendered support_sessions/_form.html.erb (5.7ms)
  Rendered service_users/show.html.erb within layouts/application (11.7ms)
  Rendered support_sessions/_form.html.erb (5.8ms)
  Rendered service_users/show.html.erb within layouts/application (12.6ms)
Completed 200 OK in 80ms (Views: 71.4ms | ActiveRecord: 4.2ms)


Completed 200 OK in 82ms (Views: 74.2ms | ActiveRecord: 2.9ms)


Started POST "/service_users/4/support_sessions" for ::1 at 2019-05-31 07:58:35 +0100
Processing by SupportSessionsController#create as HTML
  Parameters: {"utf8"=>"✓", "authenticity_token"=>"KZGBtom/9t6vYu5FyPRq3xA3Xmb6hkzG8LEA7c/qmOkUZGmtslduK+l2as5chNz7w4ikC+5fHRYh3Ak2IZwIvA==", "support_session"=>{"support_worker_id"=>"3", "location"=>"", "mode_of_delivery"=>"", "started_at"=>"", "ended_at"=>"", "total_breaks"=>""}, "commit"=>"Save session", "service_user_id"=>"4"}
  ServiceUser Load (0.5ms)  SELECT  `service_users`.* FROM `service_users` WHERE `service_users`.`id` = 4 LIMIT 1
  ↳ app/controllers/support_sessions_controller.rb:33
   (0.3ms)  BEGIN
  ↳ app/controllers/support_sessions_controller.rb:36
   (0.3ms)  ROLLBACK
  ↳ app/controllers/support_sessions_controller.rb:36
   (0.2ms)  BEGIN
  ↳ app/controllers/support_sessions_controller.rb:41
   (0.2ms)  ROLLBACK
  ↳ app/controllers/support_sessions_controller.rb:41
  Rendering support_sessions/new.html.erb within layouts/application
  SupportWorker Load (0.5ms)  SELECT `support_workers`.* FROM `support_workers` ORDER BY `support_workers`.`given_name` ASC
  ↳ app/helpers/support_sessions_helper.rb:15
  Rendered support_sessions/_form.html.erb (3.9ms)
  Rendered support_sessions/new.html.erb within layouts/application (324.5ms)
Completed 500 Internal Server Error in 336ms (ActiveRecord: 2.0ms)



ActionView::Template::Error (undefined local variable or method `support_sessions_path' for #<#<Class:0x00007ffb0925c658>:0x00007ffb083fa100>
Did you mean?  @support_session):
    2: 
    3: <%= render 'form', support_session: @support_session %>
    4: 
    5: <%= link_to 'Back', support_sessions_path %>

app/views/support_sessions/new.html.erb:5:in `_app_views_support_sessions_new_html_erb___2840892112036112501_70358075950960'
app/controllers/support_sessions_controller.rb:46:in `block (2 levels) in create'
app/controllers/support_sessions_controller.rb:40:in `create'

# Crebs:timesheets chris$ rake routes -c support_session
                           Prefix Verb   URI Pattern                                                         Controller#Action
    service_user_support_sessions GET    /service_users/:service_user_id/support_sessions(.:format)          support_sessions#index
                                  POST   /service_users/:service_user_id/support_sessions(.:format)          support_sessions#create
 new_service_user_support_session GET    /service_users/:service_user_id/support_sessions/new(.:format)      support_sessions#new
edit_service_user_support_session GET    /service_users/:service_user_id/support_sessions/:id/edit(.:format) support_sessions#edit
     service_user_support_session GET    /service_users/:service_user_id/support_sessions/:id(.:format)      support_sessions#show
                                  PATCH  /service_users/:service_user_id/support_sessions/:id(.:format)      support_sessions#update
                                  PUT    /service_users/:service_user_id/support_sessions/:id(.:format)      support_sessions#update
                                  DELETE /service_users/:service_user_id/support_sessions/:id(.:format)      support_sessions#destroy

Upvotes: 3

Views: 69

Answers (2)

Chris Kenworthy
Chris Kenworthy

Reputation: 129

Here's the code that got things working. Doesn't seem to work very well with validation but the data submits.

def create
    #render plain: params[:support_session].inspect

    # Auto-generated line:
    #@support_session = SupportSession.new(support_session_params)

    # Get service_user_id from query string
    # This is so each new record submitted can get the id of the service_user in question
    @service_user = ServiceUser.find(params[:service_user_id])

    # Copied from RoR 'Getting started' article: https://guides.rubyonrails.org/getting_started.html
    # This calls the create method on @service_user.support_session (rather than @service_user alone)
    # so the link is made between the service_user and the support_session
    # Calls private method below to safeguard input parameters (only permitted ones)
    @support_session = @service_user.support_sessions.create(support_session_params)
    #redirect_to service_user_path(@service_user)

    respond_to do |format|
      if @support_session.save
      #if @support_session
      #if @service_user.support_sessions.create(support_session_params)
      #if @service_user.support_sessions.new(support_session_params)
        #format.html { redirect_to @support_session, notice: 'Support session was successfully created.' }
        format.html { redirect_to @service_user, notice: 'Support session was successfully created.' }
        format.json { render :show, status: :created, location: @support_session }
      else
        #format.html { render :show }
        format.html { redirect_to service_user_path(@service_user), notice: 'There was an error!' }
        format.json { render json: @support_session.errors, status: :unprocessable_entity }
      end
    end
  end

Upvotes: 0

Peter Balaban
Peter Balaban

Reputation: 643

You have nested routes for support_sessions resource, so instead of

<%= link_to 'Back', support_sessions_path %>

you should use code like this to build correct path:

<%= link_to 'Back', [@service_user, :support_sessions] %>

or maybe like this one:

<%= link_to 'Back', service_user_support_sessions_path(@service_user) %>

Upvotes: 2

Related Questions