Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Model resolution registry #91

Merged
merged 2 commits into from
Feb 23, 2022

Conversation

dpsutton
Copy link
Contributor

Changes:

add a model registry from model symbol to set of namespaces that defines it. Use this registry to look up when model is used as a symbol, eg (db/select 'Foo :id 1).

Suggested breaking change

Before this change, defining multiple models with the same name and querying by model symbol would always resolve to the model defined in the "conventional" namespace as constructed with model/root-namespace. If there were multiple models defined in "unconventional" locations, it would never find either of them. The registry is checked after the inferred namespace to preserve this behavior. But it might be better to change this behavior. Rather than allowing multiple model definitions like this, perhaps we could check the inferred location and also the registered locations and ensure we do not have an ambiguous model reference.

There is probably a small chance of this happening but having models findable in non-standard locations might increase the odds of it a little bit.

ie, if you define a normal model User in the conventional location ((defmodel User :production_user_table)) and a test file defines (defmodel User :test_user_table), usages of 'User as a symbol would resolve to the production user table. It might be better to throw in this case, even if this is a remote scenario.

Toucan allows to query a model by either the model itself or the symbol
of the model `(db/select 'User :id 1)`. This lookup will look at
`models/root-namespace` for a model namespace. But this lookup will
obviously fail if you define models in namespaces that do not follow
this convention. For example multiple models in the same namespace
cannot possibly work.

This branch was added under the standard lookup so as not to be a
breaking change. In either order there is the chance for
ambiguity. Defining two models with the same name will never be
well-defined. Under this change, it will always find the model defined
by convention and never the one in a non-standard location.

This ambiguity cannot actually be fixed since there is no disambiguating
information in the query `(select 'Foo :id 1)`.
If there are multiple namespaces defining a model by the same name right
now, it will always resolve to one at the conventional location. Before
this change, no unconventional location models would ever be found by
using the symbol. This explains why the lookup by model symbol is the
second branch (so the current behavior, while unspecified before,
continues in the same manner), and why we do not choose an item if
multiple namespaces are registered (arbitrary model by set order, or
last namespace read if we just keep one namespace).
@dpsutton
Copy link
Contributor Author

If we're willing to break a bit to error when there are multiple models and a query uses a model symbol,

(defn- resolve-model-from-symbol
  [symb]
  (letfn [(sym->model-var [ns' symb']
            (try
              (requiring-resolve (symbol (str ns') (str symb')))
              (catch java.io.FileNotFoundException _e nil)))]
    (let [inferred-ns         (model-symb->ns symb)
          registered-nss      (get @models/model-sym->namespace-sym symb)
          [model-var & ambig] (->> (conj registered-nss inferred-ns)
                                   (keep #(sym->model-var % symb)))]
      (cond
        (not model-var)
        (throw (ex-info (format "Could not find model for: %s" symb)
                        {:symbol                symb
                         :configured-namespace  inferred-ns
                         :registered-namespaces registered-nss}))
        (seq ambig)
        (throw (ex-info (format "Found multiple models for: %s" symb)
                        {:symbol symb
                         :configured-namespace inferred-ns
                         :registered-namespaces registered-nss
                         :vars (conj ambig model-var)}))
        :else
        (deref model-var)))))

@camsaul
Copy link
Member

camsaul commented Feb 23, 2022

I think the expectation is that models are unique anyway so erroring if there are duplicates would be fine if you wanted to open a follow-on PR to do that... but I'll merge this as-is and cut a new release

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants