Permalink
Browse files

Support specifying foreign key and model names explicitly in belongs-…

…to and has-many.
  • Loading branch information...
1 parent c56bb7f commit 8b86ff7abcb8ffe9848d05de8de9c415dd4118bd Jim Menard committed Jun 13, 2010
View
@@ -80,6 +80,28 @@ Then you can do things like this.
Association names will have dashes converted to underscores when used in queries.
+To override the foreign key name or model name, do this:
+
+<pre><code>
+ (ns ...)
+
+ (clj-record.core/init-model
+ (:associations
+ (belongs-to account :fk account_fk_id)
+ (has-many subscriptions :fk sub_id :model subscription-model-name)))
+</code></pre>
+
+In a belongs-to, if :model is specified and :fk is not, then the foreign key
+name is inferred from the association name. For example, in
+
+<pre><code>
+ ...
+ (belongs-to mother :model person)
+ ...
+</code></pre>
+
+the foreign key name will be mother_id (not person_id).
+
h3. Validations
Do this.
View
@@ -2,10 +2,6 @@ Get something reasonable in place for testing with MySQL and Postgres.
Read and convert results lazily if a connection is available. (Not sure what exactly "available" should mean. Maybe pass in an option to force.)
-Support specifying (not inferring):
- * model-name of an association
- * foreign-key column name
-
Allow update to be called with nil values. (Should be a patch to clojure.contrib.sql I think.)
More callbacks.
@@ -40,3 +36,6 @@ Get tests out of the main src folder so we don't accidentally rely on having the
Generate fancier queries with some fancy query API.
* http://gist.github.com/64357
+Support specifying (not inferring):
+ * model-name of an association
+ * foreign-key column name
@@ -3,20 +3,27 @@
(defn expand-init-option
- "Called via init-model when an :associations option group is encountered."
- [model-name association-type-sym association-name]
+ "Called via init-model when an :associations option group is encountered.
+ Options are alternating key/value pairs."
+ [model-name association-type-sym association-name & options]
(let [assoc-fn (ns-resolve 'clj-record.associations association-type-sym)]
- (assoc-fn model-name association-name)))
+ (apply assoc-fn model-name association-name options)))
(defn has-many
"Defines an association to a model whose name is infered by singularizing association-name.
In ns foo's init-model, (has-many bars) will define find-bars and destroy-bars functions in foo,
each of which take a foo record and find or destroy bars by {:foo_id (record :id)}.
+
+ Options are alternating key/value pairs. Supported options:
+
+ :fk foreign_key_col_name
+ :model target-model-name
Called indirectly via clj-record.core/init-model."
- [model-name association-name]
- (let [associated-model-name (singularize (name association-name))
- foreign-key-attribute (keyword (str (dashes-to-underscores model-name) "_id"))
+ [model-name association-name & options]
+ (let [opts (apply hash-map options)
+ associated-model-name (str (or (:model opts) (singularize (name association-name))))
+ foreign-key-attribute (keyword (or (:fk opts) (str (dashes-to-underscores model-name) "_id")))
find-fn-name (symbol (str "find-" association-name))
destroy-fn-name (symbol (str "destroy-" association-name))]
`(do
@@ -28,11 +35,27 @@
(defn belongs-to
"Defines an association to a model named association-name.
In ns bar's init-model, (belongs-to foo) will define find-foo in bar.
+
+ Options are alternating key/value pairs. Supported options:
+
+ :fk foreign_key_col_name
+ :model target-model-name
+ If model is specified and fk is not, fk name is inferred from the
+ association name. For example,
+
+ (belongs-to mother :model person)
+
+ will assume the foreign key is mother_id.
+
Called indirectly via clj-record.core/init-model."
- [model-name association-name]
- (let [associated-model-name (str association-name)
- find-fn-name (symbol (str "find-" associated-model-name))
- foreign-key-attribute (keyword (str (dashes-to-underscores associated-model-name) "_id"))]
+ [model-name association-name & options]
+ (let [opts (apply hash-map options)
+ associated-model-name (str (or (:model opts) association-name))
+ find-fn-name (symbol (str "find-" association-name))
+ foreign-key-attribute (keyword (cond
+ (:fk opts) (:fk opts)
+ (:model opts) (str (dashes-to-underscores (str association-name)) "_id")
+ :else (str (dashes-to-underscores associated-model-name) "_id")))]
`(defn ~find-fn-name [record#]
(clj-record.core/get-record ~associated-model-name (~foreign-key-attribute record#)))))
@@ -3,7 +3,7 @@
(:require [clojure.contrib.seq-utils :as seq-utils]))
-(defn expand-init-option [model-name hook func]
+(defn expand-init-option [model-name hook func & ignored-options]
`(add-callback ~model-name ~hook ~func))
(defn add-callback [model-name hook func]
View
@@ -81,7 +81,7 @@ instance."
(if (map? attributes-or-where-params)
(to-conditions attributes-or-where-params)
attributes-or-where-params)
- select-query (format "select * from %s where %s" (table-name model-name) parameterized-where)]
+ select-query (format "select * from %s where %s" (table-name model-name) parameterized-where)]
(find-by-sql model-name (apply vector select-query values))))
(defn find-record
@@ -18,5 +18,5 @@
(defn expand-init-option
"init-model macro-expansion delegate that generates a call to add-validation."
- [model-name attribute-name]
+ [model-name attribute-name & ignored-options]
`(serialize-attribute ~model-name ~attribute-name))
@@ -35,5 +35,5 @@
(defn expand-init-option
"init-model macro-expansion delegate that generates a call to add-validation."
- [model-name attribute-name message function]
+ [model-name attribute-name message function & ignored-options]
`(add-validation ~model-name ~attribute-name ~message ~function))
@@ -1,7 +1,9 @@
(ns clj-record.associations-test
(:require
[clj-record.test-model.manufacturer :as manufacturer]
- [clj-record.test-model.product :as product])
+ [clj-record.test-model.product :as product]
+ [clj-record.test-model.person :as person]
+ [clj-record.test-model.thing-one :as thing-one])
(:use clojure.test
clj-record.test-helper))
@@ -24,6 +26,19 @@
(manufacturer/destroy-products humedai)
(is (empty? (manufacturer/find-products humedai)))))
+(defdbtest belongs-to-custom-fk-and-model
+ (let [mother-rec (person/create {:name "mom"})
+ father-rec (person/create {:name "dad"})
+ kid-rec (person/create {:name "kid" :mother_id (:id mother-rec) :father_person_id (:id father-rec)})]
+ (is (= mother-rec (person/find-mother kid-rec)))
+ (is (= father-rec (person/find-father kid-rec)))))
+
+(defdbtest has-many-custom-fk-and-model
+ (let [person-rec (person/create {:name "phred"})
+ thing-rec1 (thing-one/create {:name "one" :owner_person_id (:id person-rec)})
+ thing-rec2 (thing-one/create {:name "two" :owner_person_id (:id person-rec)})]
+ (is (= [thing-rec1 thing-rec2] (person/find-things person-rec)))))
+
(comment
(defdbtest find-records-can-do-eager-fetching-of-has-many-association
(let [manu1 (manufacturer/create (valid-manufacturer-with {:name "manu1" :grade 99}))
@@ -37,4 +52,4 @@
(is (= "manu2" (:name eager-manu2)))
(is (= [prod1 prod2] (:products eager-manu1)))
(is (= [prod3 prod4] (:products eager-manu2))))))
-)
+)
View
@@ -28,9 +28,15 @@
[:name "VARCHAR(32)" "NOT NULL"]
[:price :int]
[:manufacturer_id :int "NOT NULL"])
+ (sql/create-table :person
+ (get-id-key-spec db "person_pk")
+ [:name "VARCHAR(32) NOT NULL"]
+ [:mother_id :int] ; default fk for assoc "mother"
+ [:father_person_id :int])
(sql/create-table :thing_one
(get-id-key-spec db "thing_one_pk")
- [:name "VARCHAR(32)" "NOT NULL"])
+ [:name "VARCHAR(32)" "NOT NULL"]
+ [:owner_person_id :int]) ; will need to override fk and model name
(sql/create-table :thing_two
(get-id-key-spec db "thing_two_pk")
[:thing_one_id :int "NOT NULL"]))
@@ -0,0 +1,10 @@
+(ns clj-record.test-model.person
+ (:require clj-record.boot)
+ (:use clj-record.test-model.config))
+
+(clj-record.core/init-model
+ :table-name "person"
+ (:associations
+ (belongs-to mother :model person) ; default fk name mother_id is correct
+ (belongs-to father :fk father_person_id :model person) ; override default fk name
+ (has-many things :fk owner_person_id :model thing-one)))
@@ -5,4 +5,5 @@
(clj-record.core/init-model
:table-name "thing_one"
(:associations
- (has-many thing-twos)))
+ (has-many thing-twos)
+ (belongs-to person :fk owner_person_id)))

0 comments on commit 8b86ff7

Please sign in to comment.