Rails 4: validate a model only on specific action - validation

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

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

Order of before_save callbacks in rails

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

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

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

Change priority of Custom validations in rails model

I have implemented validations in a dependent manner, like if start_date format is invalid so i don't want to run other validation on start_date.
validates_format_of :available_start_date, :with => /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}((((\-|\+){1}\d{2}:\d{2}){1})|(z{1}|Z{1}))$/, :message => "must be in the following format: 2011-08-25T00:00:00-04:00"
This checks for a specific format and then i have custom validation methods called from which should run later.
def validate
super
check_offer_dates
end
I have used self.errors["start_date"] to check if the error object contains errors, it should skip the other validations on same parameter if it's not empty.
But the problem is def validate is called first and then the validates_format_of. How can i change this so that the flow can be achieved.
I just ran into a similar problem; this is how I fixed it using a before_save callout:
Not working (validates in wrong order - I want custom validation last):
class Entry < ActiveRecord::Base
validates_uniqueness_of :event_id, :within => :student_id
validate :validate_max_entries_for_discipline
def validate_max_entries_for_discipline
# set validation_failed based on my criteria - you'd have your regex test here
if validation_failed
errors.add(:maximum_entries, "Too many entries here")
end
end
end
Working (using before_save callout):
class Entry < ActiveRecord::Base
before_save :validate_max_entries_for_discipline!
validates_uniqueness_of :event_id, :within => :student_id
def validate_max_entries_for_discipline!
# set validation_failed based on my criteria - you'd have your regex test here
if validation_failed
errors.add(:maximum_entries, "Too many entries here")
return false
end
end
end
Note the changes:
validate_max_entries_for_discipline becomes validate_max_entries_for_discipline!
validation method now returns false on failure
validate validate_max_entries_for_discipline becomes before_save validate_max_entries_for_discipline!

Resources