Rails Skip Validation For Nested Attribute if condition is true - ruby-on-rails

How can I skip validation for nested_attribute if condition is true
aquarium.rb
has_many :fishes
accepts_nested_attributes_for :fishes,
fish.rb
belongs_to :aquarium
validates :ratio, :numericality => { :greater_than => 0 }, if: :skip_this_validation
then in aquariums_controller.rb
def some_action
#aquarium = Aquarium.new(aqua_params)
#aquarium.skip_this_validation = true # i know that this is not valid
#must skip validation for ratio and then save to DB
end

aquarium.rb
has_many :fishes
accepts_nested_attributes_for :fishes,
attr_accessor :skip_fishes_ratio_validation
fish.rb
belongs_to :aquarium
validates :ratio, :numericality => { :greater_than => 0 }, unless: proc { |f| f.aquarium&.skip_fishes_ratio_validation }
then in aquariums_controller.rb
def some_action
#aquarium = Aquarium.new(aqua_params)
#aquarium.skip_fishes_ratio_validation = true
#aquarium.save
end

You can just add the condition in method and check for the conditional validation
class Fish < ActiveRecord::Base
validates :ratio, :numericality => { :greater_than => 0 }, if: :custom_validation
private
def custom_validation
# some_condition_here
true
end
end

#aquarium.save(validate: false)
I believe skips validations on the model.

In Rails 5 you can simply pass optional: true to the belongs_to association.
So in your fish.rb, update association with the following code:
belongs_to :aquarium, optional: true
It will skip association validation if the fish object has no aquarium.

Related

undefined method `user' for #<Class:0x00007ffbd1c309b8>

I don't understand why I can't use self here?
class PayoutRequest < ApplicationRecord
validates :phone, confirmation: true, on: :create
validates :phone_confirmation, presence: true, on: :create
belongs_to :user, foreign_key: "user_id"
validates :amount, numericality: { only_integer: true, greater_than_or_equal_to: 300, smaller_than_or_equal: self.user.balance }
scope :paid, -> { where(:paid => true) }
scope :unpaid, -> { where(:paid => false) }
end
How can I write this?
Use a custom method, for example:
validate :amount_not_greater_than_balance
def amount_not_greater_than_balance
return if amount <= user.balance
errors.add(:amount, "can't be greater than balance")
end
In addition, you should probably only run this specific validation rule on: :create -- because it would presumably be totally acceptable for a payment request to become more than the user's balance, later on n the future.
Because self is not what you think it is. In case you didn't know or forgot, validation DSL is just methods called on the class itself. Here you basically call PayoutRequest.validates and pass it some parameters.
validates :amount, numericality: { only_integer: true, greater_than_or_equal_to: 300, smaller_than_or_equal: self.user.balance }
^ ^ arg ^ kw arg ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
method name just a regular hash, defined at the class level. So `self` is the class.

Rails Create new active record with association value passed in params

I have 2 rails models which look like this
class Physician < UserProfile
has_many :state_licenses, inverse_of: :physician, autosave: true, dependent: :destroy
validates :state_licenses, :length => { :minimum => 1, message: "Please select at-least one state license"}
class StateLicense < ApplicationRecord
include RailsAdminPhysicianDependencyConcern
belongs_to :physician, inverse_of: :state_licenses
belongs_to :state, optional: true
attr_accessor :client_id
validates :state, presence: { message: I18n.t("errors.choose_one", field: 'state') }
#validates :license_number, presence: { message: I18n.t("errors.blank") }
def name
return "" unless state
"#{state.try(:name)}"
end
end
In my controller, I am using the code below to create a new Physician record with a bunch of state licenses but for some reason, the state licenses I pass to the create function never make it to the Physician model
def create
physician = nil
ActiveRecord::Base.transaction do
state_licenses = params["state_licenses"]
state_licenses_For_Association = []
if (state_licenses != nil)
state_licenses.each do |state_license|
sl = {}
sl[:state_id] = state_license
state_licenses_For_Association.push(sl)
end
end
physician = Physician.create(params.permit(:first_name, :last_name, :title, :residency_board_status, :residency_specialty_id, :state_licenses => state_licenses_For_Association))
user_record = nil
super do |user|
user_record = user
user.errors.delete(:user_profile)
physician.errors.messages.each { |field, messages| messages.each {|message| user.errors.add(field, message)} }
end
raise ActiveRecord::Rollback unless user_record.persisted? && physician.persisted?
end
AdminNotificationsMailer.physician_signed_up(physician).deliver_now rescue nil
end
What am I doing wrong?
Try changing this:
physician = Physician.create(params.permit(:first_name, :last_name, :title, :residency_board_status, :residency_specialty_id, :state_licenses => state_licenses_For_Association))
to this:
physician = Physician.create(params.permit(:first_name, :last_name, :title, :residency_board_status, :residency_specialty_id).merge(state_licenses: state_licenses_For_Association)) # note the .merge call

Conditional Validation for Serialized Array

How can I create a validation where committed presence needs to be true only if the challenge's category is habit?
class Challenge < ActiveRecord::Base
CATEGORY = ['goal', 'habit']
serialize :committed, Array
validates :committed, presence: true, if: :habit # I also tried with 'habit' & 'Habit'
end
Since your category is called 'habit' (note, it is not 'Habit'), the validation would look as follows:
validates :committed, presence: true, if: ->(c) { c.category == 'habit' }
As a sidenote: I do not think your scopes will work, unless you have a column called categories in your challenges table.
Thus, if your intention was to select challenges, which have category 'habit', the scope would look as follows:
scope :habit, -> { where(category: 'habit') }
EDIT
As per discussion in comments, if you want committed to be nil instead of [""] when nothing is there, add custom validation:
validate :committed_content
private
def committed_content
self.committed = nil if committed.empty? || committed.all?(&:blank?)
true
end
validates :committed, presence: true, :if => lambda { |c| c.category == 'Habit' }
You can have a method and use it like this:
validates :committed, presence: true, if: :habit?
def habit?
self.category == 'habit'
end

Rails: How to Unit test for validations of nested attributes?

I have a the following has_many model associations in my app:
User < Company < Deed < Subtransaction,
where Deed accepts_nested_attributes_for :subtransactions. I wish to test my Model validations using Minitest and fixtures.
I have trouble, however, testing the nested attributes for validity. For example , If I clear all nested attributes using
#deed.subtransactions.clear I correctly get a test response that the model is not valid.
However
#deed.subtransactions.first.num_shares = " " does not seem to work.
How do I properly test these nested attributes for validity?
My test:
class DeedTest < ActiveSupport::TestCase
def setup
#user = users(:dagobert)
#newco = companies(:newco)
params = { :deed =>
{
:date => deeds(:inc_new).date,
:subtransactions_attributes =>
{ '1' =>
{
:shareholder_name => "Shareholder 1",
:num_shares => subtransactions(:new_subt1).num_shares
},
'2' =>
{
:shareholder_name => "Shareholder 2",
:num_shares => subtransactions(:new_subt2).num_shares
}
}
}
}
#deed = #newco.deeds.new(params[:deed])
end
# test: does not raise any error messages
test "num_shares should be present for a subtransaction" do
#deed.subtransactions.first.num_shares = nil
assert_not #deed.valid?
end
# test passing: The params are submitted correctly and pass validation
test "fixture values should be valid" do
assert #deed.valid?
end
# test passing: I can test the validity of deed attributes
test "date should be present" do
#deed.date = " "
assert_not #deed.valid?
end
# test passing: when I clear #deed.subtransactions the object is no longer valid
test "a subtransaction should be present" do
#deed.subtransactions.clear
assert_not #deed.valid?
end
end
UPDATE
Deed model:
class Deed < ActiveRecord::Base
belongs_to :company
has_many :subtransactions, dependent: :destroy
accepts_nested_attributes_for :subtransactions, allow_destroy: true,
reject_if: ->(a) { a['shareholder_name'].blank? && a['num_shares'].blank? }
validates_associated :subtransactions
validates :date, presence: true
validates :subtransactions, presence: true
end
Subtransaction model:
class Subtransaction < ActiveRecord::Base
belongs_to :deed
belongs_to :shareholder
validates :num_shares, presence: true, length: { maximum: 50 },
:numericality => { only_integer: true }
validates :shareholder_name, presence: true
# end class
end

Validate uniqueness of globalized field

I have a model with translated fields using the globalize gem and globalize-accessors gem for providing localized attributes such as name_en, name_zh_hk for a localized name field.
for example:
class Person < ActiveRecord::Base
translates :name
globalize_accessors: locales: [:en, :"zh-HK"], attributes: [:name]
# problem is:
validates :name, presence: true, uniqueness: true
end
So now name_en and name_zh_hk can get and set the value in corresponding locale correctly.
However, the validates :name validates only the name field in Person model. I also want to validate the uniqueness of the chinese input.
In short, would like a (easy) way to validate uniqueness of both name_en and name_zh_hk
** I have a form to submit both name_en and name_hk.
At the end of your person.rb model file (outside of class Person ... end, add this:
Person::Translation.class_eval do
validates_presence_of :name
validates_uniqueness_of :name
end
You have to do this
class Person < ActiveRecord::Base
translates :name
class Translation
validates :name, presence: true, uniqueness: true
end
end
I could be confused what you are asking about unique scopes:
validates :name, uniqueness: {scope: :blah}
specifically you may want to have a "PersonName" model.
PersonName
name | local | person_id
Person has_many :names
then have:
validates :name, uniqueness: { scope: :person_id }
this way if they enter a name for HK the same as the name for :en it will not be valid.
Solved with the following code.
Model
# /app/models/category.rb
...
I18n.available_locales.each do |locale|
validates :"name_#{locale}", presence: true, length: { maximum: 5 }, uniqueness: true
end
Validator
# config/initializers/associated_translations_uniqueness_validator.rb
require 'active_record'
require 'active_record/validations/uniqueness.rb'
ActiveRecord::Validations::UniquenessValidator.class_eval do
def validate_each_with_associated_translations(record, attribute, value)
klass = record.class
if klass.translates? && !klass.translated?(attribute) && klass.globalize_attribute_names.include?(attribute)
attribute_parts = attribute.to_s.rpartition('_')
raw_attribute = attribute_parts.first.to_sym
locale = attribute_parts.last.to_sym
finder_class = klass.translation_class
table = finder_class.arel_table
relation = build_relation(finder_class, table, raw_attribute, value).and(table[:locale].eq(locale))
relation = relation.and(table[klass.reflect_on_association(:translations).foreign_key].not_eq(record.send(:id))) if record.persisted?
translated_scopes = Array(options[:scope]) & klass.translated_attribute_names
untranslated_scopes = Array(options[:scope]) - translated_scopes
untranslated_scopes.each do |scope_item|
scope_value = record.send(scope_item)
reflection = klass.reflect_on_association(scope_item)
if reflection
scope_value = record.send(reflection.foreign_key)
scope_item = reflection.foreign_key
end
relation = relation.and(find_finder_class_for(record).arel_table[scope_item].eq(scope_value))
end
translated_scopes.each do |scope_item|
scope_value = record.send(scope_item)
relation = relation.and(table[scope_item].eq(scope_value))
end
if klass.unscoped.with_translations.where(relation).exists?
record.errors.add(attribute, :taken, options.except(:case_sensitive, :scope).merge(:value => value))
end
else
validate_each_without_associated_translations(record, attribute, value)
end
end
alias_method_chain :validate_each, :associated_translations
end

Resources