Rails belong_to optional flag not working

191 views Asked by At

So I have a belongs_to relationship on a model (Idea, below) to a Period model which I've given the name, ':starting_period' within the Idea model. It works fine except when creating a new Idea, if I do not supply a Period, I get an ActiveModel error (see rails console below). It seems like it's ignoring the 'optional: true' parameter to belongs_to. Now I know I have a validation on starting_period but a) the condition on that validation will not fire on a new record and b) the error text shows that it isn't that one that's being fired off. What am I missing as this model will not have a starting period when first created?

Rails 6.0.4

Ruby 3.0.2

Rails Console:

irb(main):001:0> idea = Idea.new
=> 
#<Idea:0x000055a412d45d30
irb(main):003:0> idea.title = 'test'
=> "test"
irb(main):004:0> idea.body = 'test'
=> "test"
irb(main):005:0> idea.user = User.all.first
  User Load (0.2ms)  SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ?  [["LIMIT", 1]]
=> #<User id: 1, email: "xxxxxxxxxxxxx", created_at: "2021-09-30 00:01:47.850876000 +0000", updated_at:...
irb(main):006:0> idea.save
=> false
irb(main):007:0> idea.errors
=> 
#<ActiveModel::Errors:0x000055a412bde460
 @base=
  #<Idea:0x000055a412d45d30
   id: nil,
   title: "test",
   created_at: nil,
   updated_at: nil,
   status: "draft",
   votes_count: 0,
   comments_count: 0,
   progress: "review",
   user_id: 1,
   size: 0,
   period_id: nil>,
 @errors=[#<ActiveModel::Error attribute=starting_period, type=blank, options={:message=>:required}>]>
irb(main):008:0>

Models:

class Idea < ApplicationRecord

  has_rich_text :body

  #Relationships
  has_one :action_text_rich_text, class_name: 'ActionText::RichText', as: :record #allows ransack to be able to use it

  has_many :votes, dependent: :destroy
  has_many :comments, dependent: :destroy

  belongs_to :user
  belongs_to :starting_period, optional: true, class_name: 'Period'
  
  #validations
  validates :title, presence: true
  validates :body, presence: true
  validates :user, presence: true
  #yes, I know there's a validation here on that field BUT this is NOT 
  #the error that I'm getting and the condition is not being met upon 
  #new record creation. I can remove this line and still get the 
  #exact same problem and error message so this isn't the problem
  validates :starting_period, presence: { message: "can't be blank if planned or started" }, if: :planned_or_started?
  validates :size, numericality: { only_integer: true, greater_than_or_equal_to: 0 }, presence: true
  validates :size, numericality: { greater_than: 0, message: "can't be unsized if planned or started" }, if: :planned_or_started?
...
  # upon creation, the progress will be "review" so it's not planned or started when creating it
  def planned_or_started?
    ["planned", "in_progress", "complete"].include?(progress)    
  end
class Period < ApplicationRecord

    #validations
    validates :name, presence: true
    validates :start_date, presence: true
    validates :end_date, presence: true 
    validates :capacity, presence: true
    validate :end_must_be_after_start

Migrations:

class AddStartPeriodToIdeas < ActiveRecord::Migration[6.1]
  def change
    add_reference :ideas, :period, on_delete: :nullify
  end
end

Schema:

create_table "ideas", force: :cascade do |t|
    t.string "title"
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
    t.integer "status", default: 0, null: false
    t.integer "votes_count", default: 0, null: false
    t.integer "comments_count", default: 0, null: false
    t.integer "progress", default: 0, null: false
    t.integer "user_id", null: false
    t.integer "size", default: 0, null: false
    t.integer "period_id"
    t.index ["period_id"], name: "index_ideas_on_period_id"
    t.index ["user_id"], name: "index_ideas_on_user_id"
  end

  create_table "periods", force: :cascade do |t|
    t.string "name"
    t.date "start_date"
    t.date "end_date"
    t.integer "capacity"
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
  end
1

There are 1 answers

0
Robert Neville On

It helps if after you add optional: true to your belongs_to if you rebuild your docker container so that you're running the correct code.