Rails and Model Validation - ruby-on-rails

I want to put some model level validation on the following table:
create_table :audios do |t|
t.integer :library_id, :null => false
t.string :file, :null => false, :limit => 2048
t.string :name, :limit => 512
t.timestamps
end
Does this mean, that my model, which (so far) looks like:
class Audio < ActiveRecord::Base
belongs_to :library
end
Has
class Audio < ActiveRecord::Base
validates_presence_of :library
...
or
class Audio < ActiveRecord::Base
validates_presence_of :library_id
...
?

To validate the presence of an association, use its name, without _id appended:
validates_presence_of :library
It will validate two things:
library_id is present
a Library with the given id exists
Using validates_presence_of :library_id will only give you the first validation of the two.
In addition to this, the version without _id will also correctly validate if both records are new (and therefore library_id is still unset).

Related

I want to be able to store values even if the related column is empty in rails

What we want to achieve
I'm doing the association in Rails, I'm associating the Schedule table with the PostItem table, and I'm associating the PostItem with belongs_to in the Schedule table, even if the PostItem_id associated with it in the Schedule table is empty. I would like to be able to save the file. In other words, I don't want to make the association every time; I always need the PostItem_id when I make the association within migration. Is there any way to allow saving even if the associated parent is empty?
Code
Migration File
class CreateSchedules < ActiveRecord::Migration[6.0]
def change
create_table :schedules do |t|
t.string :name
t.string :color
t.integer :start
t.integer :end
t.boolean :timed
t.boolean :long_time
t.integer :postItem_id # I want to save the file even if it is empty.
t.timestamps
end
end
end
Schedule Model
class Schedule < ApplicationRecord
belongs_to :post_item
validates :start, presence: true
validates :end, presence: true
# validates :postItem_id, allow_nill: true, allow_blank: true
end
postItem model
class PostItem < ApplicationRecord
belongs_to :post
has_many :schedules
end
After Rails 5 belongs_to associations are required by default. If you want it to be optional, you can add optional: true.
belongs_to :post_item, optional: true
You can also change this behaviour per model by doing:
self.belongs_to_required_by_default = false
https://guides.rubyonrails.org/upgrading_ruby_on_rails.html#active-record-belongs-to-required-by-default-option

In a Rails Model, will belongs_to cause a rollback if not defined?

Model:
class UserPosition < ApplicationRecord
belongs_to :user
belongs_to :job_title
end
UserPosition's schema:
t.integer :user_id
t.integer :company_id
t.integer :industry_id
t.integer :department_id
t.integer :job_title_id
t.string :job_title_custom
user_positions_controller.rb
def create
#user_position = UserPosition.find_or_create_by(user_id: current_user.id)
#user_position.update_attributes({
:industry_id => params[:industry_id],
:department_id => params[:department_id],
:job_title_id => params[:job_title_id],
:job_title_custom => params[:job_title_custom]
})
I need UserPosition to either create a record with:
user_id
job_title_custom
OR
t.integer :user_id
t.integer :company_id
t.integer :industry_id
t.integer :department_id
t.integer :job_title_id
Currently, if I try to create a UserPosition with just user_id & job_title_custom
It doesn't work, the logs show ROLLBACK the error message is:
#messages={:job_title=>["must exist"]}
What am I doing wrong here? I think it could be because job_title has a relationship defined in the model but the Rails Guide says that they are optional, so I'm not sure.
Turns out this is a new Rails 5 behavior.
"In Rails 5, whenever we define a belongs_to association, it is required to have the associated record present by default after this change.
It triggers validation error if associated record is not present."
"In Rails 4.x world To add validation on belongs_to association, we need to add option required: true ."
"Opting out of this default behavior in Rails 5. We can pass optional: true to the belongs_to association which would remove this validation check."
Full Answer: http://blog.bigbinary.com/2016/02/15/rails-5-makes-belong-to-association-required-by-default.html

Associating two models in a way both models' attributes are always pulled together in any view

I'm building a tech-specific pricegrabber-like web app, and I have a model that carries params that are common in all products. This model is called Product. Then I have one model for each type of product that I'm going to work with, for example, I'm now trying to build the first specific model, which is Videocard. So, the Product model always must have one Specific model, in this case Product-Videocard.
At this moment I'm stuck finding a way to make a product and a specific model always come tied together whenever I reach to them, be it in an index view, show view, form_for, a search, etc. But I can't picture in my head how a form will create an item and its specifications and insert a foreign key into another model with only one submit request.
Below are both models and the migrations for each:
class Product < ApplicationRecord
#belongs_to :productable, :polymorphic => true
has_one :videocard, dependent: :destroy
# Comment for this Stackoverflow question: the way I'm thinking I
# should have to make tons of has_one associations, for the other
# products. Is there a DRY way to do this?
has_many :prices, through: :stores
validates :platform, presence: { message: "should be specified." }
validates :name, presence: { message: "should be specified." }
validates_associated :videocard
end
class Videocard < ApplicationRecord
belongs_to :product
end
Migrations (shortened to make this question as clear as possible):
class CreateProducts < ActiveRecord::Migration[5.0]
def change
create_table :products do |t|
t.references :productable, polymorphic: true, index: true
t.string :name
t.string :image
t.string :partnum
t.string :manufacturer
t.string :platform #mobile, desktop, server, laptop
t.timestamps
end
end
end
class CreateVideocards < ActiveRecord::Migration[5.0]
def change
create_table :videocards do |t|
t.references :product, index: true
t.integer :memory
t.string :interface
# [...lots of attributes...]
t.integer :displayport
t.integer :minidisplayport
t.integer :tdp
t.timestamps
end
end
end
Also how can I make it so that Product only needs one has_one association, instead of using multiple ones. Remember that Videocard will have one type of specification, Memory will have other, and so on.

Trouble with self referential model in Rails

I have a model named User and I want to be able to self reference other users as a Contact. In more detail, I want a uni-directional relationship from users to other users, and I want to be able to reference an owned user of one user as a 'contact'. ALSO, i want to have information associated with the relationship, so I will be adding fields to the usercontact relation (I just edited this sentence in).
I attempted to do this while using the answer to this question as a guide.
Here is the User model:
user.rb
class User < ActiveRecord::Base
attr_accessible(:company, :email, :first_name, :last_name,
:phone_number, :position)
has_many(:user_contacts, :foreign_key => :user_id,
:dependent => :destroy)
has_many(:reverse_user_contacts, :class_name => :UserContact,
:foreign_key => :contact_id, :dependent => :destroy)
has_many :contacts, :through => :user_contacts, :source => :contact
end
I also created the model UserContact as a part of connecting contacts to users:
usercontact.rb
class UserContact < ActiveRecord::Base
belongs_to :user, :class_name => :User
belongs_to :contact, :class_name => :User
end
Here is the create_users.rb migration file i used:
create_users.rb
class CreateUsers < ActiveRecord::Migration
def change
create_table :users do |t|
t.string :first_name
t.string :last_name
t.string :phone_number
t.string :email
t.string :company
t.string :position
t.timestamps
end
end
end
And here is the create_users_contacts.rb migration:
create_users_contacts.rb
class CreateUsersContacts < ActiveRecord::Migration
def up
create_table :users_contacts, :force => true do |t|
t.integer :user_id, :null => false
t.integer :contact_id, :null => false
t.boolean :update, :null => false, :default => false
end
# Ensure that each user can only have a unique contact once
add_index :users_contacts, [:user_id, :contact_id], :unique => true
end
def down
remove_index :users_contacts, :column => [:user_id, :contact_id]
drop_table :users_contacts
end
end
However, for reasons unknown to me, I believe something has gone awry in the linking since on my users index page, I have a column using <td><%= user.contacts.count %></td>, but I get this error from the line when I attempt to load the page:
uninitialized constant User::UserContact
I think the issue may be something to do with the fact that I want to name users associated with another user as contacts, because I cannot find other examples where that is done, and as far as I can tell I am doing everything properly otherwise (similarly to other examples).
The closest similar problem that I found was outlined and solved in this question. The issue was incorrect naming of his connecting model, however I double checked my naming and it does not have that asker's problem.
Any help is appreciated, let me know if any other files or information is necessary to diagnose why this is occurring.
EDIT
After changing usercontact.rb to user_contact.rb, I am now getting this error:
PG::Error: ERROR: relation "user_contacts" does not exist
LINE 1: SELECT COUNT(*) FROM "users" INNER JOIN "user_contacts" ON "...
^
: SELECT COUNT(*) FROM "users" INNER JOIN "user_contacts" ON "users"."id" = "user_contacts"."contact_id" WHERE "user_contacts"."user_id" = 1
EDIT TWO
The issue was that my linking table, users_contacts, was misnamed, and should have been user_contacts! so I fixed it, and now it appears to work!!
You need to rename your usercontact.rb to user_contact.rb
This is naming convention rails autoload works with.

ActiveRecord Validations for Models with has_many, belongs_to associations and STI

I have four models:
User
Award
Badge
GameWeek
The associations are as follows:
User has many awards.
Award belongs to user.
Badge has many awards.
Award belongs to badge.
User has many game_weeks.
GameWeek belongs to user.
GameWeek has many awards.
Award belongs to game_week.
Thus, user_id, badge_id and game_week_id are foreign keys in awards table.
Badge implements an STI model. Let's just say it has the following subclasses: BadgeA and BadgeB.
Some rules to note:
The game_week_id fk can be nil for BadgeA, but can't be nil for BadgeB.
Here are my questions:
For BadgeA, how do I write a validation that it can only be awarded one time? That is, the user can't have more than one -- ever.
For BadgeB, how do I write a validation that it can only be awarded one time per game week?
Data model:
In my comprehension, here is your data model (click to enlarge):
Data model http://yuml.me/6afcad62
Migration:
The migration will let you meet your second requirement, at the migration level:
class CreateAwards < ActiveRecord::Migration
def self.up
create_table :awards do |t|
# custom attributes here
t.string :name
t.text :description
t.references :user, :null => false
t.references :game_week#, :null => false
t.references :badge, :null => false
t.timestamps
end
# a user can be awarded no more than a badge per week
add_index :awards, [:user_id, :badge_id, :game_week_id], :unique => true
# a user can be awarded no more than a badge for ever
#add_index :awards, [:user_id, :badge_id], :unique => true
end
def self.down
drop_table :awards
end
end
Model:
The model will let you meet both your requirements, at the model level:
class Award < ActiveRecord::Base
validate_uniqueness_of :user, :badge,
:if => Proc.new { |award| award.badge === BadgeA }
validate_uniqueness_of :user, :badge, game_week,
:unless => Proc.new { |award| award.badge === BadgeA }
#validate_uniqueness_of :user, :badge, game_week,
# :if => Proc.new { |award| award.badge === BadgeB }
end
Note:
I didn't try these snippets, but I think that the idea is here :)

Resources