Validate unique field in Rails app - ruby-on-rails

In my Rails app, I've got two models - User and Account.
A User can have many Accounts.
An Account includes a name field as shown in the schema below.
How can I validate that the name field is unique for an Account but not necessarily unique in the database? For example, User1 could have an Account with the name Cash and User2 could have a different Account but with the same name Cash.
user.rb:
class User < ActiveRecord::Base
has_many :accounts
before_save { self.email = email.downcase }
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
validates :email, presence: true,
length: { maximum: 255 },
format: { with: VALID_EMAIL_REGEX },
uniqueness: { case_sensitive: false }
has_secure_password
validates :password, presence: true, length: { minimum: 6 }
# Returns the hash digest of the given string.
def User.digest(string)
cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST :
BCrypt::Engine.cost
BCrypt::Password.create(string, cost: cost)
end
end
account.rb:
class Account < ActiveRecord::Base
belongs_to :user
has_many :balances
before_save { self.active = true }
validates :name, presence: true, length: { maximum: 250 },
uniqueness: { case_sensitive: false }
validates :user, presence: true
end
schema.rb:
....
create_table "accounts", force: :cascade do |t|
t.string "name"
t.boolean "credit"
t.boolean "active", default: true, null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "user_id"
end
create_table "balances", force: :cascade do |t|
t.decimal "balance"
t.date "date"
t.integer "account_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "users", force: :cascade do |t|
t.string "email"
t.boolean "admin"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "password_digest"
end
add_index "users", ["email"], name: "index_users_on_email", unique: true
....

You can use scope:
in your account.rb
validates :name, uniqueness: { scope: :user_id}
four you actual validates:
validates :name, presence: true, length: { maximum: 250 },
uniqueness: { case_sensitive: false, scope: :use_id }
active record validates

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

Rails belong_to optional flag not working

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.

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.

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