How to validates Rails column with self other column not blank? - ruby-on-rails

I have a mode named Exam.
There are some columns in exames:
:title
:subject_id
:exam_type
I want to know how to implement this:
class Exam < ApplicationRecord
validates :title, presence: true
validates :subject_id, presence: true, if: :no_exam_type?
def no_exam_type?
self.exam_type == ""
end
end
That is to say, I want to create a exam:
Exam.create(title: "first exam", exam_type: "something")
The subject_id must be exist, when exam_type is blank, such as exam_type="" or just do:
Exam.create(title: "first exam", subject_id: 3)
because exam_type has a default blank value.
But the subject_id doesn't necessary provide, when exam_type not blank, such as exam_type="something".
Exam.create(title: "first exam", exam_type: "something", subject_id: 3)
I test it, but no lucky.
How to do that? Thanks appreciate.

In Rails 5 belongs_to associations default to optional: false. Which means that the model will automatically validate the presence of the association.
class Thing < ApplicationRecord
belongs_to :other_thing
end
Thing.create!
# => ActiveRecord::RecordInvalid: Validation failed: other_thing can't be blank
So you need to set the association as optional and make sure the column is nullable.
class Exam < ApplicationRecord
belongs_to :subject, optional: true
validates :title, presence: true
validates :subject_id, presence: true, if: :no_exam_type?
def no_exam_type?
!self.exam_type.present?
end
end

Have you tried like this.
validates :subject_id, presence: true, :if => exam_type.blank?
you can refer the doc here to suite your requirement

use validates_presence_of instead.
validates_presence_of :subject_id, if: :no_exam_type?
def no_exam_type?
self.exam_type.nil?
end

Related

Rails inherit class shared attributes on build

assuming i have two models human and male, they both have similar attributes but not all the attributes, for example:
class Human < ApplicationRecord
has_many :males
validates :name, presence: true
validates :age, presence: true
validates :date_of_birth, presence: true
validates :identity, presence: true
end
class Male < ApplicationRecord
belongs_to :human
validates :name, presence: true
validates :age, presence: true
end
now what i want to do is when i use human.males.build i want the new male instance to inherit the shared attributes like name and age
instead of using this behavior human.males.build({attributes})
I'm thinking of overwriting the initialize (alias of build) method of Male model which will do the trick:
def initialize(attributes = {}, &block)
super(attributes, &block)
self.name = human.name unless attributes[:name].present?
self.age = human.age unless attributes[:age].present?
end
But honestly, I don't think this is a safe and generic way for the problem.

Rails conditional validation: if: doesn't working

I'm new to rails, I have a trip class with three foreign key. Two of these associate it with the same class: Place.
This is my model:
class Trip < ApplicationRecord
belongs_to :from, class_name: "Place", foreign_key: "from_id"
belongs_to :to, class_name: "Place", foreign_key: "to_id"
belongs_to :vehicle, class_name: "Vehicle", foreign_key: "vehicle_id"
validates :price, presence: true
validates :time, presence: true
validates :from_id, presence: true
validates :to_id, presence: true, if: :from_different_to?
def from_different_to?
to_id != from_id
end
end
All model tests pass except for the last one:
class TripTest < ActiveSupport::TestCase
def setup
#place1 = Place.create(name:"NewYork",cap:"11111",lat:"1234",long:"1478")
#place2 = Place.create(name:"Los Angeles", cap:"22222", lat:"1234",long:"1478")
#vehicle = Vehicle.create(targa: "ab123cd",modello:"500",marca:"Fiat", posti:5,alimentazione:"benzina")
#trip = Trip.new(price: 10, time: Time.new(2021, 10, 14, 12,03), from_id: #place1.id, to_id: #place2.id,vehicle_id: #vehicle.id)
end
...
test "Departure id and arrival id should be different" do
#trip.to_id = #place1.id
assert_not #trip.valid?
end
that result in a failure:
Failure:
TripTest#test_Departure_id_and_arrival_id_should_be_different [/media/alessandro/DATA/Universita/Magistrale/1_anno/Programmazione_concorrente/hitchhiker/test/models/trip_test.rb:45]:
Expected true to be nil or false
I'm not able to understand why.
Can someone help me?
It seems like you think validates ... if: works differently as it actually does. This line
validates :to_id, presence: true, if: :from_different_to?
translates to validate that the to_id is present if the from_different_to method returns true. When from_different_to evaluates to false then do not validate. See Rails Guides.
That means when you define
#trip.to_id = #place1.id
assert_not #trip.valid?
in your test then the first line disables the check for the presence of the to_id. No validation, no error...
I suppose what you really try to achieve is to validate that to to_id is present and from_id and to_id are not equal. This can be done with a custom validation like this:
validates :to_id, presence: true
validate :validates_places_are_different
private
def validates_places_are_different
errors.add(:to_id, "must be different to from_id") if to_id == from_id
end
I'm not able to understand why. Can someone help me?
That if conditionally enables a validation. Your to_id is the same as from_id and so to_id is not validated at all. But even if it was, to_id has a value, so there wouldn't be an error from this field.
Overall, I'm not quite sure why are you expecting a validation error here or what that error should be. In my experience, assertions like assert_not #model.valid? are virtually useless. The record might not be valid because of unrelated reasons and you'll have no idea. Personally, I assert the exact error message I'm expecting. Something along these lines (rspec syntax)
it "requires first_name" do
expected_messages = {
first_name: [:blank],
}
#model.valid?
expect(#model.errors.full_messages).to eq expected_messages
end
An alternative to that of #spickermann is that:
class Trip < ApplicationRecord
belongs_to :from, class_name: "Place", foreign_key: "from_id"
belongs_to :to, class_name: "Place", foreign_key: "to_id"
belongs_to :vehicle, class_name: "Vehicle", foreign_key: "vehicle_id"
validates :price, presence: true
validates :time, presence: true
validates :from_id, presence: true
validates :to_id, numericality: {other_than: :from_id}, if: :from_place_id?
def from_place_id
from_id
end
def from_place_id?
!from_id.nil?
end
end
Note that we have to put a control to execute the last validates only if from_id is not null, because if we doesn't do that, we vanificate the control validates :from_id, presence:true on the superior line.

Rails Admin NoMethodError in RailsAdmin::Main#delete

I have model Order:
class Order < ActiveRecord::Base
has_one :shipping_address
has_and_belongs_to_many :books
validates :first_name, :surename, :email, :street1, :country, :zipcode, presence: true
validates_format_of :email, :with => /\A([^#\s]+)#((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i
validates :zipcode, numericality: true
accepts_nested_attributes_for :shipping_address
end
and model Book:
class Book < ActiveRecord::Base
DEFAULT_PRICE = 55.15
NEXT_BOOK_PERCENT = 5
has_and_belongs_to_many :pages
has_and_belongs_to_many :orders
validates :name, presence: {message: "Name can't be blank."}
validates_length_of :name, minimum: 3, maximum: 12, message: "Sorry, we can't create this book right now. Please contact us for further information."
validate :same_letter_validation
validate :validates_for_non_alphabetic
before_save :compile
#......
end
Also I have table books_orders (book_id, order_id)
When I try do delete order from RailsAdmin panel I get next error:
NoMethodError in RailsAdmin::Main#delete
undefined method `orders_books' for #
It says that error in this line:
- #abstract_model.each_associated_children(object) do |association, child|
Have you defined that "orders_books" method anywhere in your code? If so, can you please add it to your question. If you haven't, then the root cause of your issue is just that, that you're calling the "orders_books" method but it is not yet defined
Given that you reference "#books_orders" in your question, I believe it likely that you just swapped "books_orders" and "orders_books" at some point in your code
Thanks. It's bug of a Rails 4.1.1. I have update it to 4.1.4 and all works OK.

Default value along with uniqueness validation

I have a Path model with name attribute as unique. I want to set default value as '/'
to the same.
I have done in the following manner.
class Path < ActiveRecord::Base
attr_accessible :name
validates :name, presence: true, uniqueness: true
before_validation :set_default_path
private
def set_default_path
self.name = name.presence || '/'
end
end
Domain model is designed as:
class Domain < ActiveRecord::Base
attr_accessible :name, path_id
validates :name, :path_id, presence: true
validates :name, uniqueness: {scope: :path_id}
end
But this doesn't work for consecutive inserts with a blank name for path.
path = Path.find_or_create_by_name('')
domain = Domain.new(name: 'stackoverflow.com')
domain.path = path
domain.save! # Fails with validation error
ActiveRecord::RecordInvalid:
Validation failed: Path can't be blank
Is there a robust way to achieve this ?
You should remove following callback
before_validation :set_default_path
and use validation for name as following:--
validates :name, presence: true, uniqueness: true, :if => 'name.present?'
and write a migration file to add default value to name attribute of paths table as either of followings:--
change_column :paths, :name, :string, :default => '/'
or
change_column_default :paths, :name, '/'
add condition on validation:
validates :name, presence: true
validates :name, uniqueness: true, unless: proc { |e| e.name == "/" }

Rails: is there a model shortcut for listing my many "attr_accessible" + "validates :presence => true" attributes?

In many of my rails models, I have a number of fields which are what I think of as "normal" model attributes, ie things which are set by the user, then later displayed, and are mandatory parts of a model instance. It seems kind of overly verbose to have to always do this:
class Person < ActiveRecord::Base
attr_accessible :name
attr_accessible :age
attr_accessible :height
validates :name, :presence => true
validates :age, :presence => true
validates :height, :presence => true
end
Ideally I'd like to just tell rails "everything but the auto-generated ID field should be validated present and accessible for mass assignment". How can I do that, given that it's said to be bad security practice to just make everything available for mass assignment?
Update: The existing way also seems bad in that I type my list of attributes twice, which is quite error prone.
Define your own class method, say on ActiveRecord::Base:
class ActiveRecord::Base
def self.validate_presence_and_make_accessible *args
attr_accessible *args
validates_presence_of *args
end
end
Then in your models:
class Person < ActiveRecord::Base
validate_presence_and_make_accessible :name, :age, :height
end
I suck at naming methods sometime, btw. Feel free to rename to something better.
A little less verbose way:
class Person < ActiveRecord::Base
attr_accessible :name, :age, :height
validates :name, :age, :height, :presence => true
end
Specifying only protected attributes:
class Person < ActiveRecord::Base
attr_protected :id
validates :name, :age, :height, :presence => true
end
This is simpler:
class Person < ActiveRecord::Base
attr_protected :id
validates_presence_of :name, :age, :height
end
And regarding your comment about security practice of mass assignments, I think you should read this: http://b.lesseverything.com/2008/3/11/use-attr_protected-or-we-will-hack-you

Resources