Refactoring complex phone validations
Let's summarize the business rules:


Characters other than digits, !, or x are stripped.
If any of the four types of phone numbers is provided, it must be valid.
At least one of { home, mobile, work } must be provided.


Requirement 3 is taken care of by validate :phone_provided, which is simple and fine.

The dissatisfaction comes from implementing Requirements 1 and 2.



The implementation of Requirement 1 can be simplified by making strip_bad_characters() tolerant of nil.  You can also call #read_attribute_before_type_cast() to avoid interpolation.

def strip_bad_characters(attr)
  input = read_attribute_before_type_cast(attr)
  input.gsub(/[^\d+!x]/, '') if input
end


Also, clean_phone_numbers() can use a loop:

def clean_phone_numbers
  [:phone_home, :phone_mobile, :phone_work, :phone_fax].each do |attr|
    self[attr] = strip_bad_characters(attr)
  end
end




The solution for Requirement 2 is simple: wrap the entire regular expression in \A( )?\Z to make it optional.  Then, you can get rid of all the …_is_filled? helpers.