[Racket] Ferti hydroponic nutrient solver, redux.
1
#lang racket
2
3
(provide nutrient
4
nutrient?
5
nutrient-id
6
nutrient-canonical-name
7
nutrient-french-name
8
nutrient-formula
9
(contract-out [create-nutrient! (-> string? string? string? nutrient?)]
10
[get-nutrients (-> (listof nutrient?))]
11
[get-nutrient
12
(->* () (#:id db-id? #:name string? #:formula string?) (or/c nutrient? #f))]
13
[update-nutrient!
14
(->* (nutrient?)
15
(#:name (or/c #f string?) #:formula (or/c #f string?))
16
(or/c nutrient? #f))]
17
[delete-nutrient! (-> nutrient? void?)]))
18
19
(require db
20
sql
21
"../db/conn.rkt"
22
"utils.rkt")
23
24
(struct nutrient (id canonical-name french-name formula)
25
#:transparent
26
#:property prop:custom-write
27
(λ (v out _) (fprintf out "#<~a ~a>" (nutrient-id v) (nutrient-canonical-name v))))
28
29
;; CREATE
30
31
(define (create-nutrient! canonical-name french-name formula)
32
(or (get-nutrient #:name canonical-name #:formula formula)
33
(with-tx (query-exec (current-conn)
34
(insert #:into nutrients
35
#:set [canonical_name ,canonical-name]
36
[french_name ,french-name]
37
[formula ,formula]))
38
(get-nutrient #:name canonical-name))))
39
40
;; READ
41
42
(define (row->nutrient row)
43
(match-define (vector id canonical-name french-name formula) row)
44
(nutrient id canonical-name french-name formula))
45
46
(define (get-nutrients)
47
(map row->nutrient
48
(query-rows
49
(current-conn)
50
(select id canonical_name french_name formula #:from nutrients #:order-by id #:asc))))
51
52
(define (get-nutrient #:id [id #f] #:name [canonical-name #f] #:formula [formula #f])
53
(define where
54
(cond
55
[(and id canonical-name formula)
56
(scalar-expr-qq (and (= id ,id) (= canonical_name ,canonical-name)))]
57
[id (scalar-expr-qq (= id ,id))]
58
[(and canonical-name formula)
59
(scalar-expr-qq (and (= canonical_name ,canonical-name) (= formula ,formula)))]
60
[canonical-name (scalar-expr-qq (= canonical_name ,canonical-name))]
61
[formula (scalar-expr-qq (= formula ,formula))]))
62
(match (query-maybe-row (current-conn)
63
(select id
64
canonical_name
65
french_name
66
formula
67
#:from nutrients
68
#:where (ScalarExpr:AST ,where)
69
#:order-by id
70
#:asc
71
#:limit 1))
72
[(vector id canonical-name french-name formula) (nutrient id canonical-name french-name formula)]
73
[#f #f]))
74
75
;; UPDATE
76
77
(define (update-nutrient! nutrient #:name [name #f] #:formula [formula #f])
78
(define id (nutrient-id nutrient))
79
(cond
80
[(and name formula)
81
(query-exec
82
(current-conn)
83
(update nutrients #:set [canonical_name ,name] [formula ,formula] #:where (= id ,id)))]
84
[name
85
(query-exec (current-conn) (update nutrients #:set [canonical_name ,name] #:where (= id ,id)))]
86
[formula
87
(query-exec (current-conn) (update nutrients #:set [formula ,formula] #:where (= id ,id)))]
88
[else (void)])
89
(or (get-nutrient #:id id) (error 'update-nutrient! "No nutrient with id ~a" id)))
90
91
;; DELETE
92
93
(define (delete-nutrient! nutrient)
94
(query-exec (current-conn) (delete #:from nutrients #:where (= id ,(nutrient-id nutrient)))))
95
96
(module+ test
97
(require rackunit
98
rackunit/text-ui
99
"../db/conn.rkt"
100
"../db/migrations.rkt")
101
102
(run-tests (test-suite "Nutrient model"
103
#:before (λ ()
104
(connect! #:path 'memory)
105
(migrate-all!))
106
#:after (λ () (disconnect!))
107
108
(test-case "Create nutrients"
109
(check-equal? (length (get-nutrients)) 0)
110
(create-nutrient! "Examplium" "" "Ex")
111
(check-equal? (length (get-nutrients)) 1)
112
(create-nutrient! "Ignorium" "" "Ig")
113
(check-equal? (length (get-nutrients)) 2))
114
115
(test-case "Read nutrient"
116
(define examplium (get-nutrient #:id 1))
117
(check-true (nutrient? examplium))
118
(check-equal? (nutrient-id examplium) 1))
119
120
(test-case "Read nutrient by name"
121
(define examplium (get-nutrient #:name "Examplium"))
122
(check-true (nutrient? examplium))
123
(check-equal? (nutrient-canonical-name examplium) "Examplium"))
124
125
(test-case "Read nutrient by formula"
126
(define examplium (get-nutrient #:formula "Ex"))
127
(check-true (nutrient? examplium))
128
(check-equal? (nutrient-formula examplium) "Ex"))
129
130
(test-case "Read inexisting nutrient"
131
(check-false (get-nutrient #:name "Inexistium")))
132
133
(test-case "Update nutrient name"
134
(define examplium (get-nutrient #:name "Examplium"))
135
(define examplium-nitrate (update-nutrient! examplium #:name "Examplium Nitrate"))
136
(check-equal? (length (get-nutrients)) 2)
137
(check-equal? (nutrient-canonical-name examplium-nitrate) "Examplium Nitrate")
138
(check-equal? (nutrient-formula examplium-nitrate) "Ex"))
139
140
(test-case "Update nutrient formula"
141
(define examplium-nitrate (get-nutrient #:name "Examplium Nitrate"))
142
(define examplium-sulfate (update-nutrient! examplium-nitrate #:formula "ExSO4"))
143
(check-equal? (length (get-nutrients)) 2)
144
(check-equal? (nutrient-canonical-name examplium-sulfate) "Examplium Nitrate")
145
(check-equal? (nutrient-formula examplium-sulfate) "ExSO4"))
146
147
(test-case "Update nutrient name and formula"
148
(define examplium-nitrate (get-nutrient #:name "Examplium Nitrate"))
149
(define examplium-sulfate
150
(update-nutrient! examplium-nitrate #:name "Examplium Sulfate" #:formula "ExNO3"))
151
(check-equal? (length (get-nutrients)) 2)
152
(check-equal? (nutrient-canonical-name examplium-sulfate) "Examplium Sulfate")
153
(check-equal? (nutrient-formula examplium-sulfate) "ExNO3"))
154
155
(test-case "Delete nutrient"
156
(define examplium-sulfate (get-nutrient #:name "Examplium Sulfate"))
157
(delete-nutrient! examplium-sulfate)
158
(check-equal? (length (get-nutrients)) 1)
159
(define ignorium (get-nutrient #:name "Ignorium"))
160
(delete-nutrient! ignorium)
161
(check-equal? (length (get-nutrients)) 0)))))
162