techquestion
techquestion

Reputation: 489

Edit with a belongs_to relationship

Issue

I'm encountering a problem when editing a form with a belongs_to relationship (extra_guest belongs_to age_table).

I am able to create a new extra_guest and assign it to an age_table, but I cannot get the edit/update to work as my update function returns a falseClass.--> @extra_guest.update(extra_guest_params).errors.full_messages returns undefined method `errors' for false:FalseClass

Code

models

class ExtraGuest < ApplicationRecord
  belongs_to :age_table
  validates :age_table, presence: true
end

class AgeTable < ApplicationRecord
  belongs_to :park
  has_many :extra_guests, dependent: :destroy
  validates :name, :age_from, :age_to, presence: true
  validates_associated :extra_guests
end

class Attraction < ApplicationRecord
  belongs_to :park

  has_many :extra_guests, dependent: :destroy
  accepts_nested_attributes_for :extra_guests, allow_destroy: true

  validates :name, presence: true
end

class Park < ApplicationRecord
  has_many :attractions, dependent: :destroy
  has_many :age_tables, dependent: :destroy
  validates :name, :currency, presence: true
end

extra_guests_controller

def edit
    @extra_guest = ExtraGuest.find(params[:id])
    @age_table = @extra_guest.age_table
    @age_table_list = AgeTable.where(park: @attraction.park)
  end

  def update
    @extra_guest = @attraction.extra_guests.find(params[:id])
    @age_table = AgeTable.find(params[:age_table])
    authorize @extra_guest
    if @extra_guest = @extra_guest.update(extra_guest_params)
      redirect_to root_path
    else
      @attraction = Attraction.find(params[:attraction_id])
      @extra_guest = ExtraGuest.find(params[:id])
      @age_table_list = @attraction.park.age_tables
      render 'edit'
    end
  end

private
  def extra_guest_params
    params.require(:extra_guest).permit(:name, :age_table_id,
      extra_guest_prices_attributes: [:id, :name, :price_type, :start_date, :end_date, :price, :duration, :duration_min, :duration_max, :backend_only, :weekend_extra, :_destroy])
  end

views/extra_guests/form

<%= simple_form_for [@attraction, @extra_guest] do |f|%>
     <%= f.input :age_table, :as => :select, :selected => @age_table.id, :collection => @age_table_list.map {|u| [u.name, u.id]}, :include_blank => false %>
<% f.button :submit %>

Error message + params


Couldn't find AgeTable without an ID

{"utf8"=>"✓",
 "_method"=>"patch",
 "authenticity_token"=>"l8HMnVIRybZg==",
 "extra_guest"=>
  {"age_table"=>"104",
   "extra_guest_prices_attributes"=>
    {"0"=>{"price"=>"1.0", "weekend_extra"=>"", "start_date"=>"2019-10-15", "end_date"=>"20-09-2019", "duration"=>"", "duration_min"=>"", "duration_max"=>"", "_destroy"=>"false", "id"=>"42"},
     "1"=>{"price"=>"1.0", "weekend_extra"=>"", "start_date"=>"2019-10-15", "end_date"=>"2019-10-16", "duration"=>"", "duration_min"=>"", "duration_max"=>"", "_destroy"=>"false", "id"=>"43"}}},
 "commit"=>"Save new option",
 "attraction_id"=>"185",
 "id"=>"55"}

Upvotes: 0

Views: 144

Answers (2)

arieljuod
arieljuod

Reputation: 15838

First of all, you say you have an error with this code @extra_guest.update(extra_guest_params).errors.full_messages but the code you show does not have that line.

Now, the update method returns false if it fails https://apidock.com/rails/ActiveRecord/Persistence/update

This line:

@extra_guest = @extra_guest.update(extra_guest_params)

will set @extra_guest to false if it fails, you don't need to set @extra_guest, just use if @extra_guest.update(extra_guest_params)

Using the line of code you name but it's not on the code you showed,@extra_guest.update(extra_guest_params).errors.full_messages, if there are errors then @extra_guest.update(extra_guest_params) will be false, so no .errors method is found.

you have to split it in two lines:

@extra_guest.update(extra_guest_params) # after this, @extra_guest will have the errors hash set
@extra_guest.errors.full_messages # call it on the object and not on the result value from the update method

EDIT: you are permitting age_table_id but the parameter is age_table, fix the name of the parameter to be age_table_id too

Upvotes: 1

lizzyp
lizzyp

Reputation: 13

It looks to me like you tried to use @attraction before defining it. You could fix this by moving your definition of @attraction further up in the method, but I would move it into its own method like so:

private

def attraction
   @attraction ||= Attraction.find(params[:attraction_id])
end 

Then you use the method name, which is now defined for the whole controller and invoked when you use it (as opposed to an instance variable which will just be 'nil' if you invoke it without defining it). The ||= allows the method to return the existing value of the instance variable if it is defined, rather than running the query every time the method is called. So first line of your update action would be

@extra_guest = attraction.extra_guests.find(params[:id])

I would do something similar for the other instance variables you have there (@extra_guest, @age_table, and @age_table_list should be defined in private methods separately). Incidentally, using a lot of instance variables for a single controller (you have 4 in this controller, which is a lot) is considered a bit of a code smell, but you should make something that works first and then refactor. Reference for later: https://thoughtbot.com/blog/sandi-metz-rules-for-developers

Upvotes: 0

Related Questions