# Make included\_in constrain dynamic

**URL:** <https://discourse.dry-rb.org/t/make-included-in-constrain-dynamic/1834>\
**Category:** dry-types\
**Created:** [August 12, 2024, 7:43pm UTC](https://discourse.dry-rb.org/t/make-included-in-constrain-dynamic/1834 "2024-08-12T19:43:30Z")\
**Posts on this page:** 6\
**Page:** 1

<div class="post-metadata">

**Author:** ![milushov](https://yyz1.discourse-cdn.com/flex031/user_avatar/discourse.dry-rb.org/milushov/32/901_2.png) [@milushov](https://discourse.dry-rb.org/u/milushov)\
**Post date:** [August 12, 2024, 7:43pm UTC](https://discourse.dry-rb.org/t/make-included-in-constrain-dynamic/1834/1 "2024-08-12T19:43:30Z")

</div>

Hello everyone,

I’m currently using `dry-types` version 1.7.2 in a large project, and I need to update a custom type to work with a dynamic list for the `included_in` constraint without breaking existing tests.

Previously, I had:

```ruby
Currency = String.constrained(
  included_in: Money::Currency.all.map(&:iso_code)
)

```

I changed it to:

```ruby
Currency = Types::String.constructor do |value|
  iso_codes = Money::Currency.all.map(&:iso_code)

  if iso_codes.include?(value)
    value
  else
    list = iso_codes.map { |iso_code| %["#{iso_code}"] }.join(', ')
    err_message = %["#{value}" violates constraints (included_in?([#{list}], "#{value}") failed)]
    raise Dry::Types::ConstraintError.new(value, err_message)
  end
end

```

Could someone please help me update the `Currency` constructor so that it behaves exactly like `included_in`, ensuring that all tests in the project continue to pass?

I think I’m raising the error the wrong way

---

<div class="post-metadata">

**Author:** ![alassek](https://yyz1.discourse-cdn.com/flex031/user_avatar/discourse.dry-rb.org/alassek/32/314_2.png) [@alassek](https://discourse.dry-rb.org/u/alassek)\
**Post date:** [August 12, 2024, 10:29pm UTC](https://discourse.dry-rb.org/t/make-included-in-constrain-dynamic/1834/2 "2024-08-12T22:29:17Z")

</div>

Am I correct in assuming that you are attempting to late-bind this list, so that `Money::Currency.all` can be populated at startup?

If so, you’re overthinking it:

```ruby
Currency = Types::String.constructor do |value, type|
  type.constrained(included_in: Money::Currency.all.map(&:iso_code)).call(value)
end

```

---

<div class="post-metadata">

**Author:** ![milushov](https://yyz1.discourse-cdn.com/flex031/user_avatar/discourse.dry-rb.org/milushov/32/901_2.png) [@milushov](https://discourse.dry-rb.org/u/milushov)\
**Post date:** [August 12, 2024, 11:25pm UTC](https://discourse.dry-rb.org/t/make-included-in-constrain-dynamic/1834/3 "2024-08-12T23:25:07Z")

</div>

> [@alassek](#):
>
> so that `Money::Currency.all` can be populated at startup?

At validation moment to be correct

But anyway, the code you provided works perfectly! You’re awesome, thank you a lot!

---

<div class="post-metadata">

**Author:** ![milushov](https://yyz1.discourse-cdn.com/flex031/user_avatar/discourse.dry-rb.org/milushov/32/901_2.png) [@milushov](https://discourse.dry-rb.org/u/milushov)\
**Post date:** [August 13, 2024, 11:44am UTC](https://discourse.dry-rb.org/t/make-included-in-constrain-dynamic/1834/4 "2024-08-13T11:44:09Z")

</div>

@alassek one more thing - is there a way to make the behavior exactly the same?

```auto
module NewTypes
  include Dry.Types()

  CURRENCIES = %w[EUR USD]

  Currency1 = String.constrained(
    included_in: CURRENCIES
  )

  Currency2 = String.constructor do |value, type|
    type.constrained(included_in: CURRENCIES).call(value)
  end
end

CurrencyParams1 = Dry::Schema.Params do
  required(:currency).filled(::NewTypes::Currency1)
end

CurrencyParams2 = Dry::Schema.Params do
  required(:currency).filled(::NewTypes::Currency2)
end

```

I want to keep this behaviour (to keep tests in project continue to pass):

```auto
[3] pry(main)> CurrencyParams1.call(currency: 'INVALID')

=> #<Dry::Schema::Result{:currency=>"INVALID"} errors={:currency=>["must be one of: EUR, USD"]} path=[]>

```

But I’m getting this:

```auto
[4] pry(main)> CurrencyParams2.call(currency: 'INVALID')

Dry::Types::ConstraintError: "INVALID" violates constraints (included_in?(["EUR", "USD"], "INVALID") failed)
from /bundle/ruby/3.2.0/gems/dry-types-1.7.2/lib/dry/types/constrained.rb:37:in `call_unsafe'

```

---

<div class="post-metadata">

**Author:** ![alassek](https://yyz1.discourse-cdn.com/flex031/user_avatar/discourse.dry-rb.org/alassek/32/314_2.png) [@alassek](https://discourse.dry-rb.org/u/alassek)\
**Post date:** [August 14, 2024, 1:37am UTC](https://discourse.dry-rb.org/t/make-included-in-constrain-dynamic/1834/5 "2024-08-14T01:37:37Z")

</div>

It does not appear that `Dry::Schema` will work with that approach.

I’m going to show you the _correct_ way to do this, and then the quick-and-dirty way.

Types are not meant to be mutable at runtime. If you need runtime data to alter the behavior of your validations, then a type object is the wrong level of abstraction for this.

The correct place to do this in a [dry-validator contract](https://dry-rb.org/gems/dry-validation/1.10/external-dependencies/). It is specifically built to allow for runtime dependencies.

```ruby
class MoneyField < Dry::Validation::Contract
  option :iso_codes, Types::Array.of('string')

  params do
    required(:currency).filled
  end

  rule :currency do
    unless iso_codes.include?(value)
      key.failure("must be one of: #{iso_codes.join(', ')}")
    end
  end
end

contract = MoneyField.new(
  iso_codes: Money::Currency.all.map(&:iso_code)
)

contract.call(currency: "EUR")

```

There is a _bad_ way to do this. The object passed to `:included_in` is stored by reference, and can be mutable.

```ruby
module AllowedCurrencies
  ISO_CODES = []

  def self.setup(codes)
    ISO_CODES.clear.concat(codes)
  end
end

Types = Dry.Types(default: :strict)

module Types
  Currency = String.constrained(included_in: AllowedCurrencies::ISO_CODES)
end

# Somewhere in your startup routine
Money::Currency.all.map(&:iso_code).then do |codes|
  AllowedCurrencies.setup(codes)
end

```

Since this approach relies on globally-mutable state, I would advise against this.

This is a situation where [dry-auto\_inject](https://dry-rb.org/gems/dry-auto_inject/1.0/) shines.

```ruby
module Container
  extend Dry::Core::Container::Mixin

  register "currency.iso_codes" do
    Money::Currency.all.map(&:iso_code)
  end
end

Deps = Dry::AutoInject(Container)

class MoneyField < Dry::Validation::Contract
  include Deps["currency.iso_codes"]

  params do
    required(:currency).filled
  end

  rule :currency do
    unless iso_codes.include?(value)
      key.failure("must be one of: #{iso_codes.join(', ')}")
    end
  end
end

MoneyField.new.call(currency: "USD")

```

---

<div class="post-metadata">

**Author:** ![milushov](https://yyz1.discourse-cdn.com/flex031/user_avatar/discourse.dry-rb.org/milushov/32/901_2.png) [@milushov](https://discourse.dry-rb.org/u/milushov)\
**Post date:** [August 14, 2024, 12:19pm UTC](https://discourse.dry-rb.org/t/make-included-in-constrain-dynamic/1834/6 "2024-08-14T12:19:20Z")

</div>

@alassek Thank you, man! How can I tip you to show my appreciation?
