Order of before_save callbacks in rails - activerecord

I am trying to geocode an address and I am trying to geocode on a non-persistent attribute called full_address. Here is my code:
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, }
before_save :full_address
geocoded_by :full_address
before_save :geocode, if: ->(obj){obj.full_address.present? && (obj.city_changed? || obj.state_changed?)}
def full_address
"#{city}, #{state}"
end
end

I am not sure if my before_save callbacks are firing the in the right order. Basically, how do I make sure the
before_save :full_address
fires before the other?
I looked into the around_save documentation, but I am still unsure what it's doing.

Jwan --
AR callbacks are used for a lot of reasons, and they are particularly helpful for dealing with records in the DB, but you don't need one here for for 'full_address'. Why?
Because all it is it doing is returning a string, not performing an operation on the db or altering data in any way.
It's been awhile since I've worked with geocoder gem, but in your other before_save callback, you are passing a lambdha for conditional operation.
Couple things:
1.) You're checking for presence of an interpolated string, but validating presence of the two interpolated attributes, which fires before the callback, so obj.full_address.present? should always return true because it will blow up on the validations if both or even one of those attributes isn't present. Try
before_save :geocode, if: ->(obj){ obj.city_changed? || obj.state_changed? }
+1 though on ActiveModel::Dirty implementation!
2.) Unless you're using a much older version of Rails or explicitly configuring it otherwise, ActiveModel::Dirty is autoloaded; no need for line 2.
3.) I would also try using an after_validation callback instead of the before_save. Can't guarantee that will solve the problem, but it is earlier in the callback stack. Refer to http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html

Related

How to conduct Rails model validation witih or condition?

I’m using Rails 4.2.3. I have three fields in my model — name, first_name, and last_name. would like to have a validation rule in my model that causes a save to fail if the “name” field is empty unless either the first_name or last_name field is not empty. SOoI tried
validates_presence_of :name, :unless => !:first_name.empty? or !:last_name.empty?
but this doesn’t work. I get the error below
undefined method `validate' for true:TrueClass
What is the proper way to write the validation rule above?
Everything you need to know is here.
You can write the rule by defining a separate method for it:
class Whatever < ActiveRecord::Base
validates :name, presence: true, unless: :firstname_or_surname?
def firstname_or_surname?
firstname.present? || surname.present?
end
end
Or you can use a Proc to define it inline:
class Whatever < ActiveRecord::Base
validates :name, presence: true,
unless: Proc.new { |a| a.firstname.present? || a.surname.present? }
end

Rails 4: validate a model only on specific action

I want to validate an email object only when I'm actually sending the email and not everytime I'm updating it:
class Email < ActiveRecord::Base
# Runs on every update/save method
validates :body, presence: true
def send_email
# Only run when this method is called
if body.blank?
errors.add :body, "can't be blank"
raise ActiveRecord::InvalidRecord.new(self)
end
update_column(:sent_at, Time.zone.now)
EmailMailer.new_email(self).deliver
end
end
Currently, this code will validate the body on every update, but I only want to validate when the send_email method is called. The reasoning is I want to allow users to save the email without actually sending, and this validation forces them to have a value.
I can manually check the validation in my method, but was wondering if there was a "cleaner" approach to this, something like:
validates :body, presence: true, action: :send_email # does not work, but what I want
Update 2015-08-13 15:30
I have some semi-working code, but would prefer a cleaner solution than what I have:
class Email < ActiveRecord::Base
# remove validate methods
def send_email
errors.add :body, "can't be blank" if body.blank?
# My application has some higher level controller code that handles exceptions.
raise ActiveRecord::InvalidRecord.new(self) if errors.any?
# code execution should be stopped by exception...
end
end

Rails 4: Active Record Validation error on: create

If I have some before create action like:
before_create :generate_token
private
def generate_token
self.auth_token = loop do
random_token = SecureRandom.urlsafe_base64(nil, false)
break random_token unless self.class.exists?(auth_token: random_token)
end
end
When writing validations I find validates :auth_token, presence: true, on: :create fails.
However, validates :auth_token, presence: true, on: :after_create works.
Why is this?
ref. Active Record Callback Order and Active Record Validations :on
edit: Curiously, on: :before_create passes validation, on: :create fails, and on: after_create succeeds. This is puzzling, could some please explain this?
Read the docs you linked, to:
(1) before_validation
(-) validate <======== This runs before before_create callbacks are run
(2) after_validation
(3) before_save
(4) before_create
I would consider generate the token when the object is initialized.
after_initialized :generate_token

Client side validation is not working while applying conditional validation in rails 4

I am using client side validation gem. Client side validation is not working while applying conditions.
Here is my Model Class.
validates_uniqueness_of :project_code
**validates :price, numericality: true, :if => :project_type_fixed?**
def project_type_fixed?
project_type == 'Fixed'
end
In this code validation for project_code is working fine, but for price it is not working.
Thanks in advance.
Client_side_validations does not validate conditionals out-of-the-box. What you are observing is the intended behavior.
In order to validate conditionals, you need to force them in your form:
f.number_field :price, :validate => { :numericality => true}
In addition, according to the documentation, the value needs to evaluate to true at the time the form is being generated. However, there is a hack for this: The method that determines whether the condition evaluates to true is called run_conditional (source), so you can override that method in your model:
def run_conditional(method_name_value_or_proc)
(:project_type_fixed? == method_name_value_or_proc) || super
end

Pass Validation on Form Fields Before Friendly ID Creation - Custom

I currently have two models
Location and Product
I have it configured that when a record is created in my production model, it creates a custom url based on the information it has gather from the location selected during the product creation
extend FriendlyId
friendly_id :slug_candidates, use: :slugged
def should_generate_new_friendly_id?
name_changed? or location_id_changed?
end
def slug_candidates
[
[location.media_type, location.state, location.name, :name, :sku]
]
end
What I am currently testing is when a user decides NOT to fill out those very important fields, for it to throw an error message before creating
validates :name, presence: true
validates :sku, presence: true
validates :location_id, presence: true
What is happening in my case, is that it overlooks that validator and first tries to create the slug. If I remove the custom attributes to the url creation and list as
def slug_candidates
[
[:name, :sku]
]
end
it will work fine, running the field validators first. Assuming because those two are attributes on the given model directly.
Does anyone know why this is happening? I need for the validators to be picked up first since it contains all the relevant information for the url.
Solved
def slug_candidates
if self.location_id.nil?
self.errors.add(:location_id)
else
[
[location.media_type, location.state, location.name, :name, :sku]
]
end
end

Resources