Rails Model Custom Validation Won't Work On Update - ruby-on-rails

I try to create some model validation on my Coupon Model
evrything work on create, but when i try to update data the validation won't validate
the first validation is to raise error if percentage more than 100
the second validation is to validate if date start bigger than date expired it will throw some error
my model in this code:
# frozen_string_literal: true
class Coupon < ApplicationRecord
has_many :transactions, dependent: :restrict_with_exception
has_one :battery, through: :transactions
enum :discount_type, %i[percentage nominal]
validates :code, format: { with: /\A[0-9A-Z]+\Z/ },
presence: true,
uniqueness: true,
length: { maximum: 50 },
on: :create,
allow_nil: false
validate :discount_value, :discount_type
validate :discount_value_percentage
validates :start_at, :expired_at, presence: true
validate :end_date_after_start_date
def discount_value_percentage
return unless discount_type == 'percentage' && discount_value > 100
errors.add(:discount_value, 'discount value percentage cannot over 100%')
end
def end_date_after_start_date
return if expired_at.blank? || start_at.blank?
return unless expired_at < start_at
errors.add(:expired_at, 'must be after the start date')
end
end
Please help me to resolve my problem
the validation can validate if date start bigger than date end it will throw error code

Related

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.

Only future dates - validation in Rails model

I have field date in the User model. It shuld have only future dates (from registration moment). What is the best way to validation it in the model?
I think about something like this:
class User < ActiveRecord::Base
validates :name, presence: true
validates :date, presence: true
validate :future_event
private
def future_event
errors.add(:date, "Can't be in the past!") if date < Time.now
end
end
Is it OK?
Can you think about some more simple and elegant solution?
# lib/future_validator.rb
class FutureValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
if record[attribute] < Time.now
record.errors[attribute] << (options[:message] || "can't be in the past!")
end
end
end
class User < ActiveRecord::Base
validates :name, presence: true
validates :date, presence: true
validates :future_event, future: true
end
http://guides.rubyonrails.org/active_record_validations.html#custom-validators
Take a look at the Date Validator Gem
validates :start_date,
date: { after: Proc.new { Date.current }, message: 'must be after today' },
on: :create
You might always want to investigate the differences between Date.current and Date.today - Date.current is timezone aware and will use the Timezone of your Rails app. Date.today uses system time. There can be odd differences if one is UTC and one is Eastern.
Use inclusion.
Something like this:
validates :date, inclusion: { in: Proc.new{ 1.day.from_now.. }, message: "has to be in the future" }

FactoryGirl giving me an error

I got this model:
rails g model Absence user:references company:references from:date to:date date:date category:integer hours:decimal remarks
This also generates:
FactoryGirl.define do
factory :absence do
user nil
company nil
from nil
to nil
date nil
category 0
hours "8.00"
remarks "MyString"
end
end
I set from, to and date to nil because it's either: from and to OR a certain date.
When I try this in my spec:
#absence = create(:absence, user: #company.owner, from: "2015-09-10", to: "2015-09-10", hours: 4)
I receive this error message:
NoMethodError:
undefined method `from=' for #<Absence:0x007f81f5494b88>
What could be wrong?
Edit:
When I remove the
from nil
from the factories/absences.rb I'm getting it on the next field (to) and after removing that I'm seeing the error message on category.
Edit2:
Model:
class Absence < ActiveRecord::Base
belongs_to :user
belongs_to :company
enum type: {holiday: 0, sick: 1}
validates :from, presence: true, if: '!user_id.nil?'
validates :to, presence: true, if: '!user_id.nil?'
validates :date, presence: true, if: '!company_id.nil?'
validates :hours, presence: true, if: '!user_id.nil?'
validates :hours, :numericality => { :greater_than_or_equal_to => 0 }, if: '!user_id.nil?'
validates :category, presence: true, if: '!user_id.nil?'
validates_numericality_of :company_id, allow_nil: true
validates_numericality_of :user_id, allow_nil: true
validate :company_xor_user
validate :to_date_after_from_date
validate :hours_smaller_than_workday
validate :non_overlapping
after_save :calculate_time_checks
after_destroy :calculate_time_checks_delete
DB:
https://www.evernote.com/shard/s29/sh/e8c1429d-9fa7-475b-87e8-3dc11a3f3978/08a7e7d6dfd80c6f407339cab97734c2
FINALLY found the real cause.
At first I had the Absence model created with an attribute named 'type'. This was migrated to both the development and test database. Afterwards I changed it to category and added 'from' and 'to' as well and did a rollback and migrated again (but not on test!).
By using pry
require 'pry'; binding.pry
in the test I did Absence.columns and noticed the difference.

No block given(yield) rails error on conditional validation

I have a nested form called Transaction that includes a child object called Banking Information Form. The banking information validations should not be triggered when the parent's (transaction) transaction_mechanism field is set to "cheque". For some reason when I set the transaction_mechanism to "cheque" in the form, it's still calling the banking information form validations. In the bank_information_form model I have a method called cheque_transaction? that should prevent the validations from triggering if the transaction_mechanism is "cheque", but I'm receiving an error.
The error
no block given (yield)
transaction.rb
class Transaction < ActiveRecord::Base
# Require packages
require 'time'
# Associations
belongs_to :user
has_one :banking_information_form
# Nested attributes
accepts_nested_attributes_for :banking_information_form
# Validations
validates_associated :banking_information_form
validate :check_debit_or_credit
validates :transaction_mechanism, :transaction_kind, :debit, :salesforce_id, presence: true
validate :debit, :credit,numericality: { only_integer: true }
end
banking_information_form.rb
class BankingInformationForm < ActiveRecord::Base
# Associations
# Belongs to :transaction
belongs_to :owner, foreign_key: "transaction_id", class_name: "Transaction"
# Validations
validates :financial_institution, :transit_number, :bank_account_number, presence: true, :unless => :cheque_transaction?
validates :transit_number, length: {is: 5,
too_short: "Your transit number is 5 digits long.",
too_long: "Your transit number is 5 digits long."}
validates :bank_account_number, length: {minimum: 4, maximum: 12,
too_short: "Your bank account number will be between 4 and 12 digits long.",
too_long: "Your bank account number will be between 4 and 12 digits long."}
private
def cheque_transaction?
self.transaction.transaction_mechanism == "Cheque"
end
end
class Transaction < ActiveRecord::Base
has_one :banking_information_form
# Other code
end
As you described, your banking informations will be validated only when the parent(transcactions) mechanism is not 'Cheque' :
class BankingInformationForm < ActiveRecord::Base
belongs_to :transaction #this was missing in your code.
validates :financial_institution, :transit_number,
:bank_account_number, presence: true, unless: :cheque_transaction?
# other validations here
private
def cheque_transaction?
transaction.transaction_mechanism == "Cheque"
end
end
After some more trial and error- I found a way to get my conditional validation to work. I first changed the model name from 'Transaction' to 'AccountTransaction'. I did this to comply with the conventions of rails after I found out 'Transaction' is a reserved word in rails.
Once I changed the model name, I appended my association with inverse_of: :account_transaction. More can be read about inverse_of here.
account_transaction.rb
class AccountTransaction < ActiveRecord::Base
# Require packages
require 'time'
# Associations
belongs_to :user
has_one :banking_information_form, inverse_of: :account_transaction
# Nested attributes
accepts_nested_attributes_for :banking_information_form
# Validations
validates_associated :banking_information_form
validate :check_debit_or_credit
validates :transaction_mechanism, :transaction_kind, :debit, :salesforce_id, presence: true
validate :debit, :credit,numericality: { only_integer: true }
end
banking_information_form.rb
class BankingInformationForm < ActiveRecord::Base
# Associations
belongs_to :account_transaction
# Validations
validates :financial_institution, :transit_number, :bank_account_number, presence: true, :unless => :cheque_transaction?
validates :transit_number, length: {is: 5,
too_short: "Your transit number is 5 digits long.",
too_long: "Your transit number is 5 digits long."}, :unless => :cheque_transaction?
validates :bank_account_number, length: {minimum: 4, maximum: 12,
too_short: "Your bank account number will be between 4 and 12 digits long.",
too_long: "Your bank account number will be between 4 and 12 digits long."}, :unless => :cheque_transaction?
private
def cheque_transaction?
self.account_transaction.transaction_mechanism == "Cheque"
end
end

Rails model how to check that two objects are not equal

I have 2 models, Bid & Package. How can I validate in the model that this can never be true?
bid.user == bid.item.user
so that this unit test can pass
describe "user cannot bid on their own package" do
#bid.user should_not equal #bid.item.user
end
I am trying to make sure that a user cannot bid on their own items.
Edit:
My model is
class Bid < ActiveRecord::Base
belongs_to :user
belongs_to :package
validates :user_id, presence: true
validates :package_id, presence: true
validates :amount, presence: true, numericality: { greater_than: 0 }
.....
end
class Bid < ActiveRecord::Base
validate :cannot_bid_on_self
def cannot_bid_on_self
if user.id == item.user.id
errors.add(:user, "can't bid on own item")
end
end
...

Resources