korma / Korma

Tasty SQL for Clojure.
http://sqlkorma.com
1.47k stars 222 forks source link

many-to-many without using macros #322

Closed andrusieczko closed 8 years ago

andrusieczko commented 9 years ago

Hey,

I don't want to use defentity macros because I want to have a possibility of setting up the database connection later on, no to have a global state as I'm using Stuart Sierra's reloaded workflow (http://thinkrelevance.com/blog/2013/06/04/clojure-workflow-reloaded).

Anyway, it all works fine until you use many-to-many relationship. I set up two repositories where you can see what my problem is:

  1. korma-example where you'll find an example project
  2. forked Korma - many-to-many-without-macros branch where I suggested the changes in Korma

Basically, if you want to achieve this:

(def pg-connection db-config)

(declare users roles user_roles)

(defentity users
           (database db-config)
           (entity-fields :id :username)
           (many-to-many roles :user_roles {:lfk "user_id", :rfk "role"}))
(defentity user_roles
           (database db-config)
           (entity-fields :user_id :role_id))
(defentity roles
           (database db-config)
           (entity-fields :role :description)
           (pk :role))

(select users-entity (with roles-entity (where {:role :user_roles.role_id})))

without using macros, you'd probably do sth like this:

(let [roles      (-> (create-entity "user_roles")
                     (database db-config)
                     (entity-fields :user_id :role_id))
      user-roles (-> (create-entity "user_roles")
                     (database db-config)
                     (entity-fields :user_id :role_id))
      users      (-> (create-entity "users")
                     (database db-config)
                     (entity-fields :id :username)
                     (many-to-many roles user-roles {:lfk "user_id", :rfk "role"}))]
  (select users (with roles (where {:role :user_roles.role_id}))))

The problem here is that as rel function uses reflection to resolve the variables later on during the runtime, you'll get the error "Unable to resolve var: roles-entity in this context".

Even if you use many-to-many-fn:

      users      (-> (create-entity "users")
                     (database db-config)
                     (entity-fields :id :username)
                     (many-to-many-fn roles user-roles {:lfk "user_id", :rfk "role"}))

then you'll also get an error because many-to-many-fn function expects a variable to passed.

The change I'd love to see is that I could pass the subentity as a map generated by create-entity function. With the changes I proposed in this branch, you are able to use many-to-many-fn in this example.

Unfortunately you are not able to do the two-way relationships with this approach. The only one solution that comes to my mind would be to split the definitions from creations, so the user would first create-entity and then build-entities.

Now it is resolved by the deferred variables resolution which is done using reflection that I cannot use.

Thanks to the approach with build step, the whole workflow would be much easier to understand and of course it would give more flexibility.

I hope everything what I've written is clear.

Thanks for your enormous effort in this project, Karol

P.S. I just realized I could use assoc-db-to-entity to pass the db-spec later on. I still have to use defentity but at least the connection details are defined later on. Nonetheless, if I were to read the entities definitions from some text file, I'd still need a way without macros.

immoh commented 9 years ago

Sorry for taking so long time to respond.

You can also use with-db to set the database connection when you execute the query. I would recommend setting database connection to entity only if it really is entity specific i.e. different entities live in different databases.

Korma is very macro heavy due to historical reasons. I checked your branch and it looks fine (if you provide a PR with tests I’ll merge it) but in the end of the day it just solves one problem with the entity system that wasn’t really designed to be used like you do.

andrusieczko commented 8 years ago

Hey,

I'm truly sorry for not responding for such a long time.

After digging more into your code, I agree with you that the initial design is totally different from what I wanted to achieve. Writing tests would be a huge challenge with the current state of the codebase and the added value for the community would be probably not worth it.

Moreover, that would be a very hacky solution, I don't want to contribute in that way :)

Thank you for taking time in trying to understand my problem and I'm sorry for not taking action for a long time.

All the best, Karol