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.
Related
I want to check an integer value in validation. But I want to do it depending on the value of another field. For example, if the width value exists, let it be at least 300 if the width_type is "px", but 30 if the width_type is "rate". How can I do it thanks.
If I understand correctly you are looking for conditional validation
In that case the following should work:
validates :width, numericality: { only_integer: true}
validates :width, numericality: { greater_than_or_equal_to: 300}, if: -> {width_type.to_s == 'px'}
validates :width, numericality: { greater_than_or_equal_to: 30}, if: -> {width_type.to_s == 'rate'}
If you need to handle too many other scenarios I would go with a custom validation like
MIN_WIDTH_PER_TYPE = {px: 300, rate: 30}
validates :width, numericality: { only_integer: true}
validate :_width_with_type
private
def _width_with_type
min_width = MIN_WIDTH_PER_TYPE[width_type.to_sym]
if width < min_width
errors.add(:width, "must be at least #{min_width} when using width type '#{width_type}'")
end
end
You could clean this up further as well but this should point you in the right direction.
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.
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
How can I write validation for barcode to be unique for all users where is_deleted is false and same chain?
validates :barcode, uniqueness: { conditions: -> { |record| where(is_deleted: false, chain_id: record.chain_id) } }, if: proc { |u| u.barcode.present? }
what is wrong here?
Thanks.
upd. There can be two users with same barcode with same chain_id, if one of them or both have :is_deleted => true
Rails validation have if and unless parameters which allow you to add conditions, you used it to check barcode presence properly, but you can extend it for is_deleted as well.
As to chain id, I understand that you are interested in scoping.
In your case that would be
validates :barcode, uniqueness: { scope: [:chain_id] }, if: proc { |u| u.barcode.present? && w.is_deleted.false? }
validates :barcode, uniqueness: { scope: :chain_id, conditions: ->{ where(is_deleted: false) } }, if: proc { |u| u.barcode.present? && u.active? }
I came up with a solution.
Thanks and sorry, its late here and head is not working as expected :)
Short of extracting shipping and billing addresses into an Address model, how can I remove this validation duplication?
I only want to validate the billing address if it's not the same as the shipping address. How would I go about extracting it into a module? An example would be really helpful as I never know what to include in modules, or self refers to.
validates :shipping_name, :shipping_address1, :shipping_street_number, :shipping_city, presence: true
validates :shipping_state, inclusion: { in: Address.states.values }
validates :shipping_post_code, length: { is: 5 }, numericality: { only_integer: true }
validates :billing_name, :billing_address1, :billing_street_number, :billing_city, presence: true, unless: -> { self.bill_to_shipping_address? }
validates :billing_state, inclusion: { in: Address.states.values }, unless: -> { self.bill_to_shipping_address? }
validates :billing_post_code, length: { is: 5 }, numericality: { only_integer: true }, unless: -> { self.bill_to_shipping_address? }
You can make a method and then pass in the bits that are different between the two types of addresses. In this case, the difference is the prefix word for the fields and the ability to pass in extra options.
module AddressValidator
def validates_address(type, options = {})
validates :"#{type}_name", :"#{type}_address1", :"#{type}_street_number", :"#{type}_city", {presence: true}.merge(options)
validates :"#{type}_state", {inclusion: { in: Address.states.values }}.merge(options)
validates :"#{type}_post_code", {length: { is: 5 }, numericality: { only_integer: true }}.merge(options)
end
end
class MyModel < ActiveRecord::Base
extend AddressValidator
validates_address(:shipping)
validates_address(:billing, unless: -> { self.bill_to_shipping_address? })
end