I have two before_validation callbacks. Which one gets executed first? - ruby-on-rails

Here is my company model which has geocoding. Basically, I want to make sure that the add_full_address method is called before the geocoding method because the geocoding method depends on the full_address. How do I get this to work? Are the validations run in order that they are written? I need them both to happen before validations are run because I want to verify that the latitude and longitude columns are populated because otherwise... I want the save to fail.
class Company < ActiveRecord::Base
include ActiveModel::Dirty
validates :name, :organization, :title, :state, :city, presence: true
validates :email, presence: true, length: { maximum: 255 },
format: { with: /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i, }
validates :latitude , numericality: { greater_than_or_equal_to: -90, less_than_or_equal_to: 90 }
validates :longitude, numericality: { greater_than_or_equal_to: -180, less_than_or_equal_to: 180 }
before_validation :add_full_address
before_validation :geocode, if: ->(obj){(obj.city_changed? || obj.state_changed?)}
geocoded_by :full_address do |obj, results|
if geo = results.first
obj.latitude = geo.latitude
obj.longitude = geo.longitude
end
end
def add_full_address
self.full_address = "#{city}, #{state}"
end
def d3_coordinates
slice(:longitude, :latitude)
end
end

Take a look at this: ActiveRecord::CallBacks This will give you all the callbacks you can use. Also ruby being a synchronous language, it will run from top to bottom, but of course the callbacks are run before the validations in this case.

According to the doc, validators get executed in the order that they are defined.
All before_validation callbacks will be called prior to ANY validators

Related

Ruby - Validates field presence after create

Is it possible to validate a field for presence after the initial creation?
I want to make phone number mandatory if the user wants to update their account after signing up.
validates :phone, presence: true, if: .....
if I use on: :update I can no longer authenticate until the field is filled
There are many ways to accomplish this task assuming it is a normal Rails model backed by a DB table. Off the top of my head you can do:
validates :phone,
presence: true,
if: Proc.new{ |model| model.id.present? }
Or more to the point and doesn't fail if you assign an ID before saving:
validates :phone,
presence: true,
if: Proc.new{ |model| model.persisted? }

Make Rails Validation Helpers uniqueness multiple with a group of columns

How i can validate four attributes at once using method uniqueness or if I have how?
I am use MySQL:
class Product < ApplicationRecord
validates :manufacturer,:model, :color,:carrier_plan_type, :quantity, :price, presence: true
validates :quantity, numericality: {greater_than_or_equal_to: 0}
validates :price, numericality: {greater_than_or_equal_to: 0.01}
validates :manufacturer, confirmation: { case_sensitive: false }
validates :model, format: {with: /\w+\s\w{2}\s\d{2,3}GB/i }
validates :color, format: {with: /[a-zA-Z]+|[a-zA-Z]+\s[a-zA-Z]+/i}
validates :carrier_plan_type, format: {with: /p(re|os)/}
end
This is my model Product, i would like to 4 columns (manufacturer, model, color and carrier_plan_type) with method_uniqueness. So that it is only valid to insert an instance in my database if you do not have these 4 columns repeated.
I found in Rails Guide that:
See the MySQL manual for more details about multiple column indexes or the PostgreSQL manual for examples of unique constraints that refer to a group of columns.
But i didn't understand how i can to group the columns.
To add, It is a good practice to do the validations as I did up there, if you have another opinion I am open to an idea.
Try using:
validates_uniqueness_of :carrier_plan_type, scope: [:manufacturer, :model, :color]
or
validates :carrier_plan_type, uniqueness: { scope: [:manufacturer, :model, :color] }
either should give you what you want.
In reply to the comment below try:
validates_uniqueness_of :carrier_plan_type,
scope: [:manufacturer, :model, :color, :price], if: -> { quantity == 0 }
or even
validates_uniqueness_of :carrier_plan_type,
scope: [:manufacturer, :model, :color, :price], if: :quantity_zero
private
def quantity_zero
quantity == 0
end

Rails changing a proc to a method to validate if present in model

I have a model that has a long list of attributes. Not all attributes need to be present to save, but, if they are present I want to validate them.
Currently I am adding an if: proc to the end of each validation as such:
# app/models/bio.rb
class Bio < ApplicationRecord
...
validates :age, numericality: { only_integer: true }, if: Proc.new { |a| a.age.present? }
validates :height, numericality: { only_integer: true }, if: Proc.new { |a| a.height.present? }
validates :weight, numericality: { only_integer: true }, if: Proc.new { |a| a.weight.present? }
...
end
This doesn't seem very DRY, so, I wanted to move this proc to a class method and call it via it's symbol as:
...
validates :age, numericality: { only_integer: true }, if: :has_attr(:age)
...
def has_attr(a)
#some_code.a.present?
end
I'm just unsure what the a in the proc refers to. I am assuming it's self or something similar.
How do I need to modify the method to get it to work the same as the proc? And do I need to add a ? to the method name, such as def has_attr?(a) or def has_attr(a)?? And, honestly, I'm not even sure if you can pass an argument to a symbol. Any light you can shed on this is greatly appreciated.
You can't do it that way, the :if option wants something callable (such as a Proc or lambda) or a symbol which names an instance method. There's no way to tunnel arguments down to the instance method unless you want to wrap it as in your first example.
However, in this case, you can say allow_nil: true which will have the same effect:
validates :age, numericality: { only_integer: true }, allow_nil: true
# ----------------------------------------------------^^^^^^^^^^^^^^^
Adding that will skip the validation if the age is nil and that should have the same effect as your current logic.
You could do something close to what you were trying to do with a class method that returns lambdas. Something like:
def self.has_attr(a)
->(obj) { obj.public_send(a).present? }
end
and then:
validates :age, ..., if: has_attr(:age)
You'd need to arrange for self.has_attr to be defined before your validates calls but that's not too hard, you'd probably put has_attr in a concern and include it at the top of your model class to deal with that problem and make has_attr easily available in other models as well.

removing mask before saving in DB

I have a JavaScript to mask a value in my form. I'm trying to remove this mask before save it in db, but i have problems
model:
class Partner < ApplicationRecord
belongs_to :address, dependent: :destroy
has_many :dependents
accepts_nested_attributes_for :address
# Validations #
# Validates presence of some attributes #
validates_presence_of :registry_number, :name, :secretary, :position, :base_salary, :rg, :cpf, :birthday, :email, :union_admission, :start_date
validates :registry_number, numericality: {only_integer: true}
# Validates the size of some attributes #
validates :registry_number, length: {maximum: 5}
validates :cpf, length: {is: 11}
validates :rg, length: {is: 9}
validates :phone_number, length: {maximum: 11}
:cpf.gsub(/[.\/]/, '')
end
error
undefined method `gsub' for :cpf:Symbol
Extracted source (around line #18):
16
17
18
19
20
:cpf.gsub(/[.\/]/, '')
end
How can I solve it?
As the error message tells you, the line :cpf.gsub(/[.\/]/, '') is the problem. Firstly, you're calling the method gsub on a symbol :cpf, and the method doesn't exist . Try it in irb.
Secondly, even if this wasn't the case, it wouldn't make any difference – the code is only going to be run once when the class is loaded and wouldn't be run each time you save a new Partner. You need to add a before_validation callback to run your code each time you create a new object, which would look something like this:
class Partner
#...
before_validation :unmask_cpf
#...
private
def unmask_cpf
cpf = cpf.gsub(/[.\/]/, '')
end
end
gsub is a method on strings, so you can call .to_s on the symbol first.

ActiveModel Validate form has a negative value if true else validate form has a positive value

I'm trying to validate a form field in a Rails app one of two ways, depending on the params passed in. If my object has the param allow_negative: true, I want:
validates :amount, numericality: {less_than_or_equal_to: 0}
otherwise I want:
validates :amount, numericality: {greater_than_or_equal_to: 0}
When I try to just do this:
validates :amount, numericality: {greater_than_or_equal_to: 0}, unless: :allow_negative
validates :amount, numericality: {less_than_or_equal_to: 0}, if: :allow_negative
It just executes the second instance of validates :amount
For reference, here is my entire class:
class ViewModel
include ActiveModel::Model
validates :description, presence: true
validates :amount, numericality: {greater_than_or_equal_to: 0}, unless: :allow_negative
validates :amount, numericality: {less_than_or_equal_to: 0}, if: :allow_negative
attr_reader :amount
attr_reader :description
attr_reader :allow_negative
attr_reader :order
def initialize(user, params)
#amount = params[:amount]
#description = params[:description]
#allow_negative = params[:allow_negative]
#order = Order.find(params[:order_id])
end
end
It turns out the above works if passed an actual boolean. I was passing a string of true or false. Once I converted the value of allow_negative to a true bool, it worked great.
What your code is doing is forcing either positive or negative values based on the state of :allow_negative. It would be more accurate to call it :force_negative using the structure you have there.
If you want your :allow_negative value to allow both positive and negative values, you'll want something like this:
validates :amount, numericality: true
validates :amount, numericality: {greater_than_or_equal_to: 0}, unless: :allow_negative
You may want to make a custom validation such as the following:
class ViewModel
include ActiveModel::Model
validates :amount, numericality: true
validate :force_negative
private
def force_negative
if allow_negative && amount > 0
errors.add(:amount, "must be less than or equal to 0")
elsif amount < 0
errors.add(:amount, "must be greater than or equal to 0")
end
end
end

Resources