Accessing External State from Contracts

I’m wondering what the general best practice is about accessing external data state from contracts is, particularly data which is stored in a database or external system.

Let me provide a contrived example:

class CreateChargeContract
  params do
    required(:fund_source_id).filled(:string)
    required(:currency).filled(:string)
    required(:amount).filled(:integer)
  end

  rule(:currency) do
    fund_source = FundSource.find(fund_source_id)
    key.failure(:unsupported) unless fund_source.supports_currency?(value)
  end
end

Here, our contract requires a trip to the database to retrieve a FundSource object, which is required to verify that the supplied currency value is supported.

However, it doesn’t make sense to for the contract to be responsible for retrieving this information in this way. Notably, other parts of the system may also need to retrieve this FundSource object (to perform authorization, or to access other information) which would cause redundant trips to the DB. If the way forward is to fetch the record from within the contract, I’d prefer a way of retrieving that object after it has been fetched.

That being said, there doesn’t seem to be any mention in the documentation about storing other objects within contracts, outside of their input parameters.
Similarly, I wasn’t able to find any way of passing the FundSource into this contract, beyond defining plain-old Ruby attributes (which I’m not sure is the idiomatic way of doing things).

What would be the best way to approach this sort of problem?

Contracts support dependency injection via option API. You can read about it in the docs.

Does this really fall into the domain of dependency injection?

It also poses a chicken-and-egg problem since the contract is also used to validate the fund_source_id parameter.
Also, this would require touching data coming from a remote client before running it through the validator. Is this good practice?

The rule should depend on both fields, so:

class CreateChargeContract
  params do
    required(:fund_source_id).filled(:string)
    required(:currency).filled(:string)
    required(:amount).filled(:integer)
  end

  rule(:currency, :fund_source_id) do
    fund_source = FundSource.find(values[:fund_source_id])
    key.failure(:unsupported) unless fund_source.supports_currency?(value)
  end
end

No, it’s not. Why not validate currency via contract? So whatever that supports_currency? is doing could be part of the contract.

Yeah, looks like I forgot about that when putting together the example.
I can’t exactly use actual code from our code base.

Unforunately, I don’t see how I’d be able to do that.

The FundSource class in the example refers to the parent type of a family of classes. Each type of fund source implements its supports_currency? method differently.
The most trivial case defines a list of supported currencies. One particular type of fund source requires an HTTP request to determine whether a currency is permitted, as it varies between records but is managed by an external service.

Ultimately something is going to need to call the supports_currency? method, or otherwise invoke the logic it refers to.

If that’s the case than you don’t want to use your model validations inside a contract that also validate values used to fetch an instance of that model, precisely due to the chicken-egg problem. It seems like there are a couple of separate concerns here that you’ve tried mixing in a single contract.

First, you want to check fund_source_id, it is not used to create a new charge, it is used to fetch a fund contract object in order to validate charge’s attributes. This means it makes no sense to mix those 3 attributes inside a single contract. From what I see here you’re dealing with a query (which depends on fund_source_id attribute being valid), and a contract that would receive fund_source object as its external dependency, in order to validate currency.