Home > database >  Rails5: Recently changed my form into a Nested form and now need to access the arrays in the nest. H
Rails5: Recently changed my form into a Nested form and now need to access the arrays in the nest. H

Time:11-08

(Apologies if this question is simple...I've been at this for a few days now)

So...I've recently changed my model, controller and partial _form.html.erb files to implement a Nested form...

And now my jbuilder file needs to be refactored but I'm a noob on pulling out nested values...

original index.json.jbuilder

json.array! @assignments do |assignment|
 
  date_format = assignment.all_day_event? ? '%Y-%m-%d' : '%Y-%m-%dT%H:%M:%S'
  #binding.pry
  json.description assignment.description <---this used to work before I added my Nested Form
  
  # REQUIRED
  json.id assignment.id
  json.title assignment.roster_title
  json.start assignment.event_date.strftime('%Y-%m-%d') assignment.start_time.strftime('T%H:%M:%S')
  json.end assignment.event_date.strftime('%Y-%m-%d') assignment.end_time.strftime('T%H:%M:%S')
  # REQUIRED end
  shift_id = assignment.volunteer_shift_id.nil? ? assignment.volunteer_task_type_id : assignment.volunteer_shift_id
  json.volunteer_shift_id shift_id
# DATE
  json.date assignment.event_date.strftime('%Y-%m-%d')

# TIME
  json.start_time assignment.start_time.strftime('T%H:%M:%S')
  json.end_time assignment.end_time.strftime('T%H:%M:%S')
  json.color assignment.color unless assignment.color.blank?
#   json.color assignment.skedj_style unless assignment.skedj_style.blank?
  json.allDay assignment.all_day_event? ? true : false

  json.update_url assignment_path(assignment, method: :patch)
  json.edit_url edit_assignment_path(assignment)
end

This is the error I am getting...

 Rendering assignments/index.json.jbuilder
  Assignment Load (2.1ms)  SELECT "assignments".* FROM "assignments" INNER JOIN "volunteer_shifts" ON "volunteer_shifts"."id" = "assignments"."volunteer_shift_id" INNER JOIN "volunteer_events" ON "volunteer_events"."id" = "volunteer_shifts"."volunteer_event_id" WHERE ("volunteer_events"."date" BETWEEN $1 AND $2)  [["date", "2021-01-05"], ["date", "2021-01-06"]]
  Rendered assignments/index.json.jbuilder (27.8ms)
Completed 500 Internal Server Error in 48ms (ActiveRecord: 6.3ms)


DEPRECATION WARNING: #original_exception is deprecated. Use #cause instead. (called from real_exception at /home/fonso/.rbenv/versions/2.5.8/lib/ruby/gems/2.5.0/gems/better_errors-0.3.2/lib/better_errors/error_page.rb:82)

NoMethodError - undefined method `volunteer_event' for nil:NilClass:
  app/models/assignment.rb:46:in `description'
  app/views/assignments/index.json.jbuilder:12:in `block in _app_views_assignments_index_json_jbuilder__1085023644391212852_37504860'
  (gem) jbuilder-2.11.2/lib/jbuilder.rb:339:in `block (2 levels) in _map_collection'
  (gem) jbuilder-2.11.2/lib/jbuilder.rb:346:in `_scope'
  (gem) jbuilder-2.11.2/lib/jbuilder.rb:339:in `block in _map_collection'
  (gem) activerecord-5.0.7.2/lib/active_record/relation/delegation.rb:38:in `map'
  (gem) activerecord-5.0.7.2/lib/active_record/relation/delegation.rb:38:in `map'
  (gem) jbuilder-2.11.2/lib/jbuilder.rb:338:in `_map_collection'
  (gem) jbuilder-2.11.2/lib/jbuilder.rb:216:in `array!'
  (gem) jbuilder-2.11.2/lib/jbuilder/jbuilder_template.rb:90:in `array!'
  app/views/assignments/index.json.jbuilder:8:in `_app_views_assignments_index_json_jbuilder__1085023644391212852_37504860'

My new assignments_controller has been modified in the "new" action and the assignment_params

class AssignmentsController < ApplicationController
  before_action :set_assignment, only: [:show, :edit, :update, :destroy]
  skip_before_action :verify_authenticity_token #TODO refactor this line to be very specific

  # GET /assignments or /assignments.json
  def index
    # @assignments = Assignment.limit(20)
    # @assignments = Assignment.where(start: params[:start]..params[:end])
    @assignments = Assignment.date_range(params[:start]..params[:end])

  end

  # GET /assignments/1 or /assignments/1.json
  def show
  end

  # GET /assignments/new
  def new
    @assignment = Assignment.new
    #fixme: build goes here
    @assignment.volunteer_shift.build
    @my_url = {:action => "create", :id => params[:id]}

  end

  # GET /assignments/1/edit
  def edit
  end

  # POST /assignments or /assignments.json
  def create
    @assignment = Assignment.new(assignment_params)

    # error wants contact.id not contact_id ???

    respond_to do |format|
      if @assignment.save
        format.html { redirect_to @assignment, notice: "Assignment was successfully created." }
        format.json { render :show, status: :created, location: @assignment }
      else
        format.html { render :new, status: :unprocessable_entity }
        format.json { render json: @assignment.errors, status: :unprocessable_entity }
      end
    end
  end


  # PATCH/PUT /assignments/1 or /assignments/1.json
  def update

    @assignment.update(assignment_params)
  end

  # DELETE /assignments/1 or /assignments/1.json
  def destroy
    @assignment.destroy
    # NOTE: comment original out 4 now
    # respond_to do |format|
    #   format.html { redirect_to assignments_url, notice: "Assignment was successfully destroyed." }
    #   format.json { head :no_content }
    # end
  end

  private
    # Use callbacks to share common setup or constraints between actions.
    def set_assignment
      @assignment = Assignment.find(params[:id])
    end

    # Only allow a list of trusted parameters through.
    def assignment_params
      #fixme: ,volunteer_shift_attributes: [:???, :???, :???] <--- insert this below?
      params.require(:assignment).permit(:title,
        :redirect_to, :set_date, :date_range, :contact_id,
        :start_time, :end_time, :start, :end, :attendance_type_id, :notes,
        :call_status_type_id, :closed, :lock_version, :color,
         volunteer_shift_attributes: [:volunteer_task_type_id,:roster_id,:program_id,:set_description,:set_date,:id,:destroy])
    end
end

Here are the three related model associations

class VolunteerEvent < ApplicationRecord
  belongs_to :volunteer_default_event
  validates_presence_of :date
  has_many :volunteer_shifts, :dependent => :destroy
  has_many :resources_volunteer_events, :dependent => :destroy
  validates_associated :volunteer_shifts
...
end
class VolunteerShift < ApplicationRecord
  validates_presence_of :roster_id #fixme: remove these validations?
  validates_presence_of :end_time #fixme: remove these validations?
  validates_presence_of :start_time #fixme: remove these validations?
  belongs_to :volunteer_default_shift

  belongs_to :volunteer_task_type
  has_many :assignments
  belongs_to :program
  belongs_to :roster
  belongs_to :volunteer_event

  has_many :contact_volunteer_task_type_counts, :primary_key => 'volunteer_task_type_id', :foreign_key => 'volunteer_task_type_id' #:through => :volunteer_task_type

...
end
class Assignment < ApplicationRecord
  attr_accessor :volunteer_shift #,:contact_id ???
  belongs_to :volunteer_shift
  has_one :volunteer_task_type, :through => :volunteer_shift, :source => :volunteer_task_type
  belongs_to :contact ,optional: true
  validates_presence_of :volunteer_shift
  validates_associated :volunteer_shift
  belongs_to :attendance_type
  belongs_to :call_status_type
  validates_presence_of :set_date, :if => :volshift_stuck
  # validates_existence_of :contact, :allow_nil => true <----THIS IS BAD
  accepts_nested_attributes_for :volunteer_shift, allow_destroy: true

  #fixme: Nodule::DelegationError -
  # This error appears when trying to show the "Add New Assignment" model on the /assignments view
  delegate :set_date, :set_date=, :to => :volunteer_shift
  delegate :set_description, :set_description=, :to => :volunteer_shift

  scope :date_range, lambda { |range|
    joins(volunteer_shift: :volunteer_event)
        .where(volunteer_shifts: { volunteer_events: { date: range } })
  }
  scope :is_after_today, lambda {||
    { :conditions => ['(SELECT date FROM volunteer_events WHERE id = (SELECT volunteer_event_id FROM volunteer_shifts WHERE id = assignments.volunteer_shift_id)) > ?', Date.today] }
  }
  scope :on_or_after_today, lambda {||
    { :conditions => ['(SELECT date FROM volunteer_events WHERE id = (SELECT volunteer_event_id FROM volunteer_shifts WHERE id = assignments.volunteer_shift_id)) >= ?', Date.today] }
  }
  scope :not_cancelled, -> { where('(attendance_type_id IS NULL OR attendance_type_id NOT IN (SELECT id FROM attendance_types WHERE cancelled = \'t\'))')}
  scope :roster_is_limited_by_program, -> {where("roster_id IN (SELECT id FROM rosters WHERE limit_shift_signup_by_program = 't')").joins(:volunteer_shift)}

  attr_accessor :attendance_type_id



  #fixme: fix all red dots on this page
  def real_programs
    return [] unless self.volunteer_shift&.roster
    return [] unless self.volunteer_shift.roster.limit_shift_signup_by_program
    self.volunteer_shift.roster.skeds.select{|x| x.category_type == "Program"}.map{|x| x.name}
  end

# TODO: find all time_range_s methods and either pull out to DRY or give unique names
  def time_range_s
    return "" unless start_time and end_time
    (start_time.strftime("%I:%M")   ' - '   end_time.strftime("%I:%M")).gsub( ':00', '' ).gsub( ' 0', ' ').gsub( ' - ', '-' ).gsub(/^0/, "")
  end

  def description
    self.volunteer_shift.volunteer_event.date.strftime("%D")   " "   self.time_range_s   " "   self.slot_type_desc
  end

  def roster_title
    self.volunteer_shift.roster.name
  end

  #fixme: date / set_date is fracked since old code was ported over
   def date
     volunteer_shift.date
   end

  #full calendar uses this method name....see the assignment.json.jbuilder
  def event_date
    self.date
  end

  def slot_type_desc
    b = (self.volunteer_shift.volunteer_task_type_id.nil? ? self.volunteer_shift.volunteer_event.description : self.volunteer_shift.volunteer_task_type.description)
    b = b   " (#{self.volunteer_shift.description})" if self.volunteer_shift.description and self.volunteer_shift.description.length > 0
    b
  end

  def display_name
    ((!(self.volunteer_shift.description.nil? or self.volunteer_shift.description.blank?)) ? self.volunteer_shift.description   ": " : "")   self.contact_display
  end

  def cancelled?
    (self.attendance_type&.cancelled)
  end

  def attended?
    (self.attendance_type and !self.attendance_type.cancelled)
  end

  def contact_display
    if self.closed
      "(closed)"
    elsif contact_id.nil?
      return "(available)"
    else
      self.contact.display_name   "(#{self.voltask_count})"
    end
  end

  def <=>(other)
    self.date <=> other.date
  end

  # arieljuod thinks this is suspect. "belongs_to :contact" should have taken care of setting contact object
  # def contact_id=(new_val)
  #   self.write_attribute(:contact_id, new_val)
  #   self.contact = Contact.find_by_id(new_val.to_i)
  # end

  def contact_id_and_by_today
    # Unless the contact id is empty, or the event date is after today.
    !(contact_id.nil? || self.volunteer_shift.volunteer_event.date > Date.today)
  end

  def voltask_count
    self.contact_volunteer_task_type_count ? self.contact_volunteer_task_type_count.attributes["count"] : 0
  end

  before_validation :set_values_if_stuck
  def set_values_if_stuck
    return unless volshift_stuck
    volunteer_shift.set_values_if_stuck(self)
  end

  after_destroy { |record| if record.volunteer_shift&.stuck_to_assignment; record.volunteer_shift.destroy; else VolunteerShift.find_by_id(record.volunteer_shift_id).fill_in_available; end}
  after_save {|record| if record.volunteer_shift&.stuck_to_assignment; record.volunteer_shift.save; end}
  after_save { |record| VolunteerShift.find_by_id(record.volunteer_shift_id).fill_in_available }

  # def volunteer_shift_attributes=(attrs) #fixme: why is this not getting called on volunteer_events/create_shift?
  #   self.volunteer_shift.attributes=(attrs) # just pass it up
  # end

#fixme: where is stuck_to_assignment ??? WTF?
  def volshift_stuck
    self.volunteer_shift&.stuck_to_assignment
  end

  def first_time_in_area?
    if self.contact and self.volunteer_shift and self.volunteer_shift.volunteer_task_type
      !ContactVolunteerTaskTypeCount.has_volunteered?(self.contact_id, self.volunteer_shift.volunteer_task_type_id)
    else
      false
    end #  and self.contact_id_changed? moved outside because we use update_attributes
  end

#for fullcalendar
  def all_day_event?
    self.start_time == self.start_time.midnight && self.end_time == self.end_time.midnight ? true : false
  end
end

...

end

I don't know what's goin on..why is the jbuilder breaking now? is it because of the nested form? is it a relation in my models? Something with my assignment_params???

How do I re-write my index.json.jbuilder?

(Note: I suspect I have to do some nested array stuff in my json file, but I've not done that before...https://stackoverflow.com/questions/35749301/get-nested-arrays-in-json)

UPDATE:

So, I tested a theory out If I switch branches to an earlier version pre-nested form....all my json output gets displayed. If I switch back to the nested form branch I do not get any json output that my main page uses to display. (note: I have not been able to add a new record as the link to add a new record is on the main page...that now does not get displayed). Process of elimination points to the model associations recently added when I was trying to create a Nested Form. But if I comment those lines out... line 2 and 12... in my assignment model...

class Assignment < ApplicationRecord
  # attr_accessor :volunteer_shift #,:contact_id ???  <----TURN THIS OFF AND JSON DISPLAYS AGAIN
  belongs_to :volunteer_shift
  has_one :volunteer_task_type, :through => :volunteer_shift, :source => :volunteer_task_type
  belongs_to :contact ,optional: true
  validates_presence_of :volunteer_shift #belongs_to takes care of this now
  validates_associated :volunteer_shift
  belongs_to :attendance_type
  belongs_to :call_status_type
  validates_presence_of :set_date, :if => :volshift_stuck #belongs_to takes care of this now??
  #accepts_nested_attributes_for :volunteer_event, allow_destroy: true <----TURN THIS OFF

The json outputs to the main screen again no problems.

UPDATE 2:

if I just comment out line 2 of assignment.rb all the json data reapers

CodePudding user response:

The error message

NoMethodError - undefined method `volunteer_event` for nil:NilClass:
app/models/assignment.rb:50:in `description'

tells that in the description method in line 50 of the app/models/assignment.rb file is called volunteer_event on an instance of nil:NilClass

def description
  self.volunteer_shift.volunteer_event.date.strftime("%D")   " "   self.time_range_s   " "   self.slot_type_desc
end

That means that self.volunteer_shift must return nil and that there are Assignments in your database that do not have a volunteer_shift assigned.

The fix depends on your application, but a simple solution might be to have description returning nil when there is no volunteer_shift – like this

def description
  return unless volunteer_shift

  self.volunteer_shift.volunteer_event.date.strftime("%D")   " "   self.time_range_s   " "   self.slot_type_desc
end

CodePudding user response:

UPDATE

  attr_accessor :volunteer_shift #,:contact_id ??? 

this line will have been hiding the

  belongs_to :volunteer_shift

declaration which is why there was no data to show, you should still check and validate though as discussed and as below.


@spickermann has pretty much nailed your problem in their answer but it is not a complete solution.

  1. Your data got messed up because you were creating assignment records with no volunteer_shift records associated.

This was allowed to happen because previous checks to stop this from happening are outdated and only work as expected in older versions of rails with the validates_presence_of methods, so they need updating.

  1. You need to have tests to ensure that this never happens again and you will be notified straight away if it does happen again. rspec, capybara and factorybot with guard are my tools of choice in place of standard rails testing framework however, the tools you use should be your preference, just make sure you have test coverage for the creation and updating of assignment records that ensure you have a valid volunteer_shift record, that way you will know when you have fixed the original problem that caused this in the first place and not just when you have dealt with this specific issue.

  2. Lastly you either need to fix your data, or you need to add checks to any method that uses volunteer_shift in your assignment model as suggested by @spickermann by checking for volunteer_shift not being nil, and perhaps do both of these things.

  • Related