# Custom predicates again question

**URL:** <https://discourse.dry-rb.org/t/custom-predicates-again-question/1051>\
**Category:** dry-validation\
**Created:** [May 7, 2020, 3:16pm UTC](https://discourse.dry-rb.org/t/custom-predicates-again-question/1051 "2020-05-07T15:16:12Z")\
**Posts on this page:** 3\
**Page:** 1

<div class="post-metadata">

**Author:** ![b43](https://yyz1.discourse-cdn.com/flex031/user_avatar/discourse.dry-rb.org/b43/32/515_2.png) [@b43](https://discourse.dry-rb.org/u/b43)\
**Post date:** [May 7, 2020, 3:16pm UTC](https://discourse.dry-rb.org/t/custom-predicates-again-question/1051/1 "2020-05-07T15:16:12Z")

</div>

Hello, I need to `filter` field data before coercion, but for this logic, I need a custom predicate.

```
class User::Create < OperationBase
  option :params, DryTypes::Strict::Hash

  class Contract < ContractBase
    option :record

    params do
      optional(:phone).filter(:phone_plausible?).filled(DryTypes::NormalizedPhone)
    end
    rule(:phone).validate(:unique)
  end

  def call
    user = User.new
    result = yield(validate(record: user))
    user.assign_attributes(result.to_h)
    user.save!
    Success(user)
  end
end

```

but I cant archive this

```
module DryCustomPredicates
  include Dry::Logic::Predicates

  predicate(:phone_plausible?) do |value|
    Phony.plausible?(value)
  end
end

class ContractBase < Dry::Validation::Contract
  # config.predicates = Dry::Schema::PredicateRegistry.new(DryCustomPredicates)
  config.messages.backend = :i18n
  # config.messages.load_paths << 'config/locales/validation_errors.yml'
  config.messages.default_locale = :ru

  register_macro(:unique) do
    key.failure(:non_unique_value) unless record.class
      .where.not(id: record.id)
      .where(keys.first => value).empty?
  end

  register_macro(:email_format) do
    unless /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i.match?(value)
      key.failure(:invalid_url_format)
    end
  end
end

```

suggest, please any true way for archive this logic, without monkey patching.  
Currently I use non-strict coercion

```
  NormalizedPhone = DryTypes::Strict::String.constructor do |phone|
    Phone::Normalize.call(phone)
  end

class Phone::Normalize
  include Service

  param :phone_string, DryTypes::Strict::String

  def call
    return @phone_string unless can_normalize?
    Phony.format(
      normalized_phone,
      format: '+%<cc>s%<ndc>s%<local>s',
      spaces: '',
      local_spaces: ''
    )
  end

  private

  def can_normalize?
    !@phone_string.blank? && Phony.plausible?(phone_string_without_trash)
  end

  def normalized_phone
    Phony.normalize(phone_string_without_trash)
  end

  def phone_string_without_trash
    @phone_string.gsub(/-/, '').gsub(/^8/, '7')
  end
end

```

and then use `rule`  
rule(:phone) do  
key.failure(:invalid\_phone\_format) if key? && !Phony.plausible?(value)  
end

but this method seems not beautiful

---

<div class="post-metadata">

**Author:** ![solnic](https://yyz1.discourse-cdn.com/flex031/user_avatar/discourse.dry-rb.org/solnic/32/779_2.png) [@solnic](https://discourse.dry-rb.org/u/solnic)\
**Post date:** [May 9, 2020, 10:06am UTC](https://discourse.dry-rb.org/t/custom-predicates-again-question/1051/2 "2020-05-09T10:06:41Z")

</div>

I believe you can achieve what you want in a simpler way:

```ruby
require "dry/validation"
require "phony"

module Types
  include Dry.Types()

  NormalizedPhone = String.constructor { |value| Phony.normalize(value) if Phony.plausible?(value) }
end

class Contract < Dry::Validation::Contract
  params do
    optional(:phone).filter(:str?, :filled?).value(Types::NormalizedPhone)
  end
end

contract = Contract.new

puts contract.(phone: "12501602703").inspect
# #<Dry::Validation::Result{:phone=>"12501602703"} errors={}>

puts contract.(phone: "12345678").errors.to_h.inspect
# {:phone=>["must be a string"]}

puts contract.(phone: []).errors.to_h.inspect
# {:phone=>["must be a string"]}

puts contract.(phone: nil).errors.to_h.inspect
# {:phone=>["must be a string"]}

puts contract.(phone: "").errors.to_h.inspect
# {:phone=>["must be filled"]}

puts contract.(phone: "123-oops-45678").errors.to_h.inspect
# {:phone=>["must be a string"]}

```

This doesn’t require a custom predicate and keeps usage of `Phony` in a single place.

---

<div class="post-metadata">

**Author:** ![b43](https://yyz1.discourse-cdn.com/flex031/user_avatar/discourse.dry-rb.org/b43/32/515_2.png) [@b43](https://discourse.dry-rb.org/u/b43)\
**Post date:** [May 11, 2020, 9:08am UTC](https://discourse.dry-rb.org/t/custom-predicates-again-question/1051/3 "2020-05-11T09:08:49Z")

</div>

Thanks! Now, dry-rb fully meets my requirements, I’m happy 🙂
