Rails belong_to optional flag not working - ruby-on-rails

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

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.

Related

Getting ActiveRecord::InvalidForeignKey: PG::ForeignKeyViolation while trying to create record with associations

I'm trying to create a record with associations.
class Migration < ActiveRecord::Migration[6.1]
create_table "users", id: :serial do |t|
t.string "first_name"
t.string "last_name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "profiles", id: :serial do |t|
t.integer "user_id"
t.integer "profile_type", default: 0, null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["user_id"], name: "index_profiles_on_user_id"
end
add_foreign_key "profiles", "users"
end
class User < ActiveRecord::Base
has_one :profile
with_options presence: { message: "can't be blank" } do
validates :first_name, length: { minimum: 3, maximum: 20 }
validates :last_name, length: { minimum: 3, maximum: 50 }
end
end
class Profile < ActiveRecord::Base
belongs_to :user
end
ActiveRecord::Base.transaction do
user = User.new(first_name: "first_name", last_name: "last_name")
user.build_profile
user.save
end
Sometimes I get an error
ActiveRecord::InvalidForeignKey: PG::ForeignKeyViolation: ERROR: insert or update on table "profiles" violates foreign key constraint "fk_rails_057a5c93f0" DETAIL: Key (user_id)=(:some_id) is not present in table "users". But I am not quite sure what the issue is. I expect the transaction rollback after inserting into users if there is any issue.
rails 6.1.5; ruby 3.1.2; postgres 12

ActiveRecord How to combine two conditions on a where with a join

I have two tables with a join table in between:
Series
create_table "series", force: :cascade do |t|
t.string "title", null: false
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
end
class Series < ApplicationRecord
include PgSearch::Model
has_many :makers
has_many :creators, through: :makers
has_many :sources, dependent: :destroy
has_many :entries, through: :sources
validates :title, presence: true
pg_search_scope :search_by_title, against: {
title: 'A',
title_en: 'B',
title_en_jp: 'C'
}
end
Maker
create_table "makers", force: :cascade do |t|
t.bigint "series_id", null: false
t.bigint "creator_id", null: false
t.bigint "creator_type", null: false
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.index ["creator_id"], name: "index_makers_on_creator_id"
t.index ["series_id", "creator_id", "creator_type"], name: "index_makers_on_series_and_creator_and_type", unique: true
t.index ["series_id"], name: "index_makers_on_series_id"
end
# frozen_string_literal: true
class Maker < ApplicationRecord
extend Enumerize
belongs_to :series
belongs_to :creator
validates :series, uniqueness: { scope: %i[creator creator_type] }
enumerize :creator_type, in: {
author: 1,
artist: 2
}, predicates: true, scope: true
end
Creator
create_table "creators", force: :cascade do |t|
t.string "name", null: false
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.index ["name"], name: "index_creators_on_name"
end
class Creator < ApplicationRecord
include PgSearch::Model
has_many :makers
has_many :series, through: :makers
pg_search_scope :search_by_title, against: :name
end
Based on the tables above, I wanted to create a where method, which would find Series for specific makers. The issue is for series that have two makers, usually an author and artist.
So while this code works, to find a Series for a specific creator:
def self.find_by_creators(title, creators)
where(title: title)
.joins(makers: :creator)
.where(
makers: {
creator_type: :author,
creators: { name: creators[:authors] }
}
)
end
when trying to add another where, nothing gets returned:
def self.find_by_creators(title, creators)
where(title: title)
.joins(makers: :creator)
.where(
makers: {
creator_type: :author,
creators: { name: creators[:authors] }
}
)
.where(
makers: {
creator_type: :artist,
creators: { name: creators[:artists] }
}
)
end

Why is my controller not saving my object in the database?

I am saving an entry object for CalendarEntry which is my model, but in the view when I click "Done" for some reason the object doesn't save.
In my point of view my controller is fine, but maybe the issue is there:
Controller
def create
#entry = CalendarEntry.new(entries_params)
binding.pry
if #entry.save
render 'admins/calendar_entries/index'
else
render 'admins/calendar_entries/new'
end
end
def entries_params
conversions
params.require(:calendar_entry).permit(:entry_type, :entry_level, :visible, :title, :publication_date, :expiration_date, :content, :phonenumber, :website, :state, :city, :address)
end
def conversions
params[:calendar_entry][:entry_type] = params[:calendar_entry][:entry_type].to_i
params[:calendar_entry][:entry_level] = params[:calendar_entry][:entry_level].to_i
end
Console
As you see in the console is asking me for two values "calendar_categories" and "calendar_entry_categories", but how it's supposed to ask it because my "CalendarEntry" only ask for the values in there,
P.D. The id, created_at and updated_at is generated automatically.
Update July/17 - 11:12pm
Schema defined here:
create_table "calendar_categories", force: :cascade do |t|
t.string "name"
t.string "description"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "calendar_entries", force: :cascade do |t|
t.integer "entry_type"
t.integer "entry_level"
t.boolean "visible"
t.string "title"
t.datetime "publication_date"
t.datetime "expiration_date"
t.text "content"
t.string "phonenumber"
t.string "website"
t.string "state"
t.string "city"
t.string "address"
t.string "profile_picture"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "calendar_entry_categories", force: :cascade do |t|
t.bigint "calendar_entry_id"
t.bigint "calendar_category_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["calendar_category_id"], name: "index_calendar_entry_categories_on_calendar_category_id"
t.index ["calendar_entry_id"], name: "index_calendar_entry_categories_on_calendar_entry_id"
end
Model defined here:
class CalendarEntry < ApplicationRecord
scope :visible, -> { where(visible: true) }
scope :invisible, -> { where(visible: false) }
scope :expired, -> { where('expiration_date < ?', Time.zone.now) }
scope :active, -> { where('expiration_date >= ?', Time.zone.now) }
has_many :calendar_entry_categories, dependent: :destroy
has_many :calendar_categories, through: :calendar_entry_categories
enum entry_type: %i[event program]
enum entry_level: %i[municipal statal federal injuve]
mount_uploader :profile_picture, CalendarEntryProfilePictureUploader
validates :entry_type, :entry_level, :visible, :title,
:expiration_date, :content, :phonenumber, :website, :state, :city,
:address, :calendar_categories,
:calendar_entry_categories, presence: true
validates :publication_date, presence: true, on: :update
validates :title, uniqueness: true
validates :phonenumber, numericality: { only_integer: true }
validates :phonenumber, length: { is: 10 }
validates_inclusion_of :entry_type, in: CalendarEntry.entry_types
validates_inclusion_of :entry_level, in: CalendarEntry.entry_levels
validate :expiration_date_range
before_validation :init, on: :create
private
def init
self.publication_date ||= Time.zone.now
end
def expiration_date_range
return if !expiration_date.nil? && expiration_date > publication_date
errors.add(:expiration_date, :past_expiration_date)
end
end
It looks like you're trying to validate the presence of calendar_categories and calendar_entry_categories in your model validations.
You won't be able to validate their presence, considering a CalendarEntryCategory cannot exist until a CalendarEntry exists, and a CalendarCategory might not always exist when a CalendarEntry is created.
Therefore, to get this to work, all you should have to do is remove
:calendar_categories, :calendar_entry_categories from the presence: true validations in your CalendarEntry model.

ActiveRecord::Fixture::FixtureError: table "users" has no column named "status"

I'm testing my UsersController which make users activated and deactivated.
I'm use fixtures and when I start my tests, I see error:
ActiveRecord::Fixture::FixtureError: table "users" has no column named "status".
The same mistake I see on the "locale" field, but on the "role" field, all is well, why?
P.S. Seeds work well
Migrate file (db/migrate/20160915110112_create_users.rb):
class CreateUsers < ActiveRecord::Migration[5.0]
def change
create_table :users do |t|
t.string :firstname, null: false
t.string :lastname, null: false
t.string :email, null: false
t.string :login, index: true, null: false
t.string :password, null: false
t.integer :genres, null: false
t.integer :role, null: false
t.integer :status, null: false
t.integer :locale, null: false
t.datetime :created_at, null: false
end
end
end
User model (app/models/user.rb):
class User < ApplicationRecord
validates :login, presence: true, uniqueness: true
validates :email, presence: true, uniqueness: true
validates :genres, presence: true, numericality: true
validates :role, presence: true
enum status: { activated: 1, deactivated: 2 }
enum locale: { ru: 1, en: 2 }
enum role: { creator: 1, admin: 2 }
before_create do
self.firstname.capitalize!
self.lastname.capitalize!
self.password = password_generate
self.status = :activated
self.locale = :ru
end
...
private
def password_generate
#password = SecureRandom.base64(8)
BCrypt::Password.create(#password)
end
end
fixture file (spec/fixtures/users.yml):
alex_foo:
id: 1
firstname: Alex
lastname: Foo
email: alex#foo.com
login: foo
password: $2a$10$E5lj6OpvHwJ4ry3zHt7QrOl0cYgeuWwbvSKXtNZeGAvPIxRVnKnmW
genres: 46
role: 1
status: 1
locale: 1
created_at: 2016-10-19 08:16:07.539325
li_huan:
id: 2
firstname: Li
lastname: Huan
email: li#huan.chi
login: huan
password: $2a$10$E5lj6OpvHwJ4ry3zHt7QrOl0cYgeuWwbvSKXtNZeGAvPIxRVnKnmW
genres: 54
role: 2
status: 1
locale: 1
created_at: 2016-10-19 08:16:07.539325
seeds.rb:
User.create(
firstname: 'init',
lastname: 'user',
login: 'init',
email: 'example#mail',
genres: 511,
role: 1
)
Try removing the column
rails g migration removeStatusFromUsers
and then adding it again.

TypeError No implicit conversion of (my class name)::ActiveRecord_Relation into Integer

Whenever I try to access a model I have created named KlassAttribute. I am getting the following error and I don't fully understand why, or how to rectify this issue.
no implicit conversion of KlassAttribute::ActiveRecord_Relation into Integer
It happens for example when I load the rails console and type in KlassAttribute. I have 3 models and 3 tables. A klass has many klass_attributes and a klass_attribute has many klass_values.
The KlassAttribute constant is only being partially loaded due to the exception. I think my models and schema is correct, but what is causing my issue? If I omit 'validates' in my model, the model can be accessed just fine.
Schema.rb
create_table "klasses", force: :cascade do |t|
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "klass_attributes", force: :cascade do |t|
t.integer "klass_id"
t.string "name"
t.string "destination"
t.integer "priority"
t.integer "value_type", default: 0
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
add_index "klass_attributes", ["klass_id"], name: "index_klass_attributes_on_klass_id"
create_table "klass_values", force: :cascade do |t|
t.string "key"
t.string "value"
t.integer "klass_attribute_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
add_index "klass_values", ["klass_attribute_id"], name: "index_klass_values_on_klass_attribute_id"
klass.rb
class Klass < ActiveRecord::Base
has_many :klass_attributes
accepts_nested_attributes_for :klass_attributes, allow_destroy: true
validates :name, presence: true, uniqueness: true, length: { maximum: 255 }
end
klass_attribute.rb
class KlassAttribute < ActiveRecord::Base
belongs_to :klass
has_many :klass_values
accepts_nested_attributes_for :klass_values, allow_destroy: true
validates :name, presence: true, length: { maximum: 255 }
end
klass_value.rb
class KlassValue < ActiveRecord::Base
belongs_to :klass_attribute
validates :key, length: { maximum: 255 }
validates :value, presence: true, length: { maximum: 255 }
end
I figured out why I had the error.
I had declared an enum in the KlassAttribute model that I didn't include in the source code above.
enum value_type: { array: 0, hash: 1}
The hash key in this enum was the cause of the error, so I renamed hash to something more appropriate and voila!

Resources