dbee-active_record 2.0.4 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +14 -13
  3. data/.ruby-version +1 -1
  4. data/.travis.yml +4 -10
  5. data/CHANGELOG.md +24 -0
  6. data/Guardfile +2 -1
  7. data/README.md +1 -1
  8. data/dbee-active_record.gemspec +21 -8
  9. data/exe/.gitkeep +0 -0
  10. data/lib/dbee/providers/active_record_provider.rb +3 -3
  11. data/lib/dbee/providers/active_record_provider/expression_builder.rb +96 -55
  12. data/lib/dbee/providers/active_record_provider/maker.rb +37 -0
  13. data/lib/dbee/providers/active_record_provider/{expression_builder/constraint_maker.rb → makers/constraint.rb} +12 -12
  14. data/lib/dbee/providers/active_record_provider/{expression_builder/order_maker.rb → makers/order.rb} +9 -9
  15. data/lib/dbee/providers/active_record_provider/makers/select.rb +81 -0
  16. data/lib/dbee/providers/active_record_provider/makers/where.rb +111 -0
  17. data/lib/dbee/providers/active_record_provider/version.rb +1 -1
  18. data/spec/db_helper.rb +134 -14
  19. data/spec/dbee/providers/active_record_provider/expression_builder_spec.rb +90 -0
  20. data/spec/dbee/providers/active_record_provider/makers/where_spec.rb +260 -0
  21. data/spec/dbee/providers/active_record_provider_spec.rb +112 -14
  22. data/spec/fixtures/active_record_snapshots/five_table_query.yaml +1 -0
  23. data/spec/fixtures/active_record_snapshots/multiple_same_table_query_with_static_constraints.yaml +1 -0
  24. data/spec/fixtures/active_record_snapshots/one_table_empty_query.yaml +11 -0
  25. data/spec/fixtures/active_record_snapshots/one_table_query.yaml +1 -0
  26. data/spec/fixtures/active_record_snapshots/one_table_query_with_ascending_sort.yaml +1 -0
  27. data/spec/fixtures/active_record_snapshots/one_table_query_with_descending_sort.yaml +1 -0
  28. data/spec/fixtures/active_record_snapshots/one_table_query_with_filters.yaml +9 -8
  29. data/spec/fixtures/active_record_snapshots/one_table_query_with_limit.yaml +1 -0
  30. data/spec/fixtures/active_record_snapshots/one_table_query_with_multiple_sorts.yaml +1 -0
  31. data/spec/fixtures/active_record_snapshots/partitioner_example_1_query.yaml +1 -0
  32. data/spec/fixtures/active_record_snapshots/partitioner_example_2_query.yaml +1 -0
  33. data/spec/fixtures/active_record_snapshots/reverse_polymorphic_query.yaml +1 -0
  34. data/spec/fixtures/active_record_snapshots/two_table_query.yaml +1 -0
  35. data/spec/fixtures/active_record_snapshots/two_table_query_with_aggregation.yaml +72 -0
  36. data/spec/fixtures/active_record_snapshots/two_table_query_with_pivoting.yaml +89 -0
  37. data/spec/fixtures/models.yaml +112 -84
  38. data/spec/spec_helper.rb +13 -2
  39. metadata +96 -28
  40. data/lib/dbee/providers/active_record_provider/expression_builder/select_maker.rb +0 -33
  41. data/lib/dbee/providers/active_record_provider/expression_builder/where_maker.rb +0 -68
@@ -1,5 +1,6 @@
1
1
  model_name: Theaters, Members, and Movies
2
2
  query:
3
+ from: theaters
3
4
  fields:
4
5
  - key_path: members.demos.phone_numbers.phone_number
5
6
  display: PHONE NUMBER
@@ -1,5 +1,6 @@
1
1
  model_name: Theaters, Members, and Movies
2
2
  query:
3
+ from: theaters
3
4
  fields:
4
5
  - key_path: id
5
6
  - key_path: name
@@ -0,0 +1,11 @@
1
+ model_name: Patients
2
+ query:
3
+ from: patients
4
+ sqlite_readable: |+
5
+ SELECT "patients".* FROM "patients" "patients"
6
+ sqlite_not_readable: |+
7
+ SELECT "t0".* FROM "patients" "t0"
8
+ mysql_readable: |+
9
+ SELECT `patients`.* FROM `patients` `patients`
10
+ mysql_not_readable: |+
11
+ SELECT `t0`.* FROM `patients` `t0`
@@ -1,5 +1,6 @@
1
1
  model_name: Theaters, Members, and Movies
2
2
  query:
3
+ from: theaters
3
4
  fields:
4
5
  - key_path: id
5
6
  display: 'ID #'
@@ -1,5 +1,6 @@
1
1
  model_name: Theaters, Members, and Movies
2
2
  query:
3
+ from: theaters
3
4
  fields:
4
5
  - key_path: id
5
6
  - key_path: name
@@ -1,5 +1,6 @@
1
1
  model_name: Theaters, Members, and Movies
2
2
  query:
3
+ from: theaters
3
4
  fields:
4
5
  - key_path: id
5
6
  - key_path: name
@@ -1,5 +1,6 @@
1
1
  model_name: Theaters, Members, and Movies
2
2
  query:
3
+ from: theaters
3
4
  fields:
4
5
  - key_path: id
5
6
  display: 'ID #'
@@ -67,8 +68,8 @@ sqlite_readable: |+
67
68
  "theaters"."name" LIKE 'A%' AND
68
69
  "theaters"."name" LIKE '%m%' AND
69
70
  "theaters"."active" = 'false' AND
70
- "theaters"."active" IN ('t', 'f', NULL) AND
71
- "theaters"."inspected" IN ('f', 't', NULL) AND
71
+ ("theaters"."active" IS NULL OR "theaters"."active" IN ('t', 'f')) AND
72
+ ("theaters"."inspected" IS NULL OR "theaters"."inspected" IN ('f', 't')) AND
72
73
  "theaters"."created_at" <= '2019-03-04' AND
73
74
  "theaters"."created_at" < '2018-03-04' AND
74
75
  "theaters"."created_at" >= '2001-03-04' AND
@@ -85,8 +86,8 @@ sqlite_not_readable: |+
85
86
  "t0"."name" LIKE 'A%' AND
86
87
  "t0"."name" LIKE '%m%' AND
87
88
  "t0"."active" = 'false' AND
88
- "t0"."active" IN ('t', 'f', NULL) AND
89
- "t0"."inspected" IN ('f', 't', NULL) AND
89
+ ("t0"."active" IS NULL OR "t0"."active" IN ('t', 'f')) AND
90
+ ("t0"."inspected" IS NULL OR "t0"."inspected" IN ('f', 't')) AND
90
91
  "t0"."created_at" <= '2019-03-04' AND
91
92
  "t0"."created_at" < '2018-03-04' AND
92
93
  "t0"."created_at" >= '2001-03-04' AND
@@ -103,8 +104,8 @@ mysql_readable: |+
103
104
  `theaters`.`name` LIKE 'A%' AND
104
105
  `theaters`.`name` LIKE '%m%' AND
105
106
  `theaters`.`active` = 'false' AND
106
- `theaters`.`active` IN (TRUE, FALSE, NULL) AND
107
- `theaters`.`inspected` IN (FALSE, TRUE, NULL) AND
107
+ (`theaters`.`active` IS NULL OR `theaters`.`active` IN (TRUE, FALSE)) AND
108
+ (`theaters`.`inspected` IS NULL OR `theaters`.`inspected` IN (FALSE, TRUE)) AND
108
109
  `theaters`.`created_at` <= '2019-03-04' AND
109
110
  `theaters`.`created_at` < '2018-03-04' AND
110
111
  `theaters`.`created_at` >= '2001-03-04' AND
@@ -121,8 +122,8 @@ mysql_not_readable: |+
121
122
  `t0`.`name` LIKE 'A%' AND
122
123
  `t0`.`name` LIKE '%m%' AND
123
124
  `t0`.`active` = 'false' AND
124
- `t0`.`active` IN (TRUE, FALSE, NULL) AND
125
- `t0`.`inspected` IN (FALSE, TRUE, NULL) AND
125
+ (`t0`.`active` IS NULL OR `t0`.`active` IN (TRUE, FALSE)) AND
126
+ (`t0`.`inspected` IS NULL OR `t0`.`inspected` IN (FALSE, TRUE)) AND
126
127
  `t0`.`created_at` <= '2019-03-04' AND
127
128
  `t0`.`created_at` < '2018-03-04' AND
128
129
  `t0`.`created_at` >= '2001-03-04' AND
@@ -1,5 +1,6 @@
1
1
  model_name: Theaters, Members, and Movies
2
2
  query:
3
+ from: theaters
3
4
  fields:
4
5
  - key_path: id
5
6
  - key_path: name
@@ -1,5 +1,6 @@
1
1
  model_name: Theaters, Members, and Movies
2
2
  query:
3
+ from: theaters
3
4
  fields:
4
5
  - key_path: id
5
6
  - key_path: name
@@ -1,5 +1,6 @@
1
1
  model_name: Partitioner Example 1
2
2
  query:
3
+ from: dogs
3
4
  fields:
4
5
  - key_path: id
5
6
  sqlite_readable: |+
@@ -1,5 +1,6 @@
1
1
  model_name: Partitioner Example 2
2
2
  query:
3
+ from: owners
3
4
  fields:
4
5
  - key_path: id
5
6
  - key_path: dogs.id
@@ -1,5 +1,6 @@
1
1
  model_name: Reverse Polymorphic Example
2
2
  query:
3
+ from: animals
3
4
  fields:
4
5
  - key_path: id
5
6
  - key_path: type
@@ -1,5 +1,6 @@
1
1
  model_name: Theaters, Members, and Movies
2
2
  query:
3
+ from: theaters
3
4
  fields:
4
5
  - key_path: id
5
6
  - key_path: name
@@ -0,0 +1,72 @@
1
+ model_name: Patients
2
+ query:
3
+ from: patients
4
+ fields:
5
+ - key_path: id
6
+ display: 'ID #'
7
+ - key_path: first
8
+ display: First Name
9
+ - key_path: patient_payments.amount
10
+ display: Ave Payment
11
+ aggregator: ave
12
+ - key_path: patient_payments.amount
13
+ display: Number of Payments
14
+ aggregator: count
15
+ - key_path: patient_payments.amount
16
+ display: Max Payment
17
+ aggregator: max
18
+ - key_path: patient_payments.amount
19
+ display: Min Payment
20
+ aggregator: min
21
+ - key_path: patient_payments.amount
22
+ display: Total Paid
23
+ aggregator: sum
24
+
25
+ sqlite_readable: |+
26
+ SELECT
27
+ "patients"."id" AS 'ID #',
28
+ "patients"."first" AS 'First Name',
29
+ AVG("patient_payments"."amount") AS 'Ave Payment',
30
+ COUNT("patient_payments"."amount") AS 'Number of Payments',
31
+ MAX("patient_payments"."amount") AS 'Max Payment',
32
+ MIN("patient_payments"."amount") AS 'Min Payment',
33
+ SUM("patient_payments"."amount") AS 'Total Paid'
34
+ FROM "patients" "patients"
35
+ LEFT OUTER JOIN "patient_payments" "patient_payments" ON "patient_payments"."patient_id" = "patients"."id"
36
+ GROUP BY "patients"."id", "patients"."first"
37
+ sqlite_not_readable: |+
38
+ SELECT
39
+ "t0"."id" AS 'c0',
40
+ "t0"."first" AS 'c1',
41
+ AVG("t1"."amount") AS 'c2',
42
+ COUNT("t1"."amount") AS 'c3',
43
+ MAX("t1"."amount") AS 'c4',
44
+ MIN("t1"."amount") AS 'c5',
45
+ SUM("t1"."amount") AS 'c6'
46
+ FROM "patients" "t0"
47
+ LEFT OUTER JOIN "patient_payments" "t1" ON "t1"."patient_id" = "t0"."id"
48
+ GROUP BY "t0"."id", "t0"."first"
49
+ mysql_readable: |+
50
+ SELECT
51
+ `patients`.`id` AS 'ID #',
52
+ `patients`.`first` AS 'First Name',
53
+ AVG(`patient_payments`.`amount`) AS 'Ave Payment',
54
+ COUNT(`patient_payments`.`amount`) AS 'Number of Payments',
55
+ MAX(`patient_payments`.`amount`) AS 'Max Payment',
56
+ MIN(`patient_payments`.`amount`) AS 'Min Payment',
57
+ SUM(`patient_payments`.`amount`) AS 'Total Paid'
58
+ FROM `patients` `patients`
59
+ LEFT OUTER JOIN `patient_payments` `patient_payments` ON `patient_payments`.`patient_id` = `patients`.`id`
60
+ GROUP BY `patients`.`id`, `patients`.`first`
61
+ mysql_not_readable: |+
62
+ SELECT
63
+ `t0`.`id` AS 'c0',
64
+ `t0`.`first` AS 'c1',
65
+ AVG(`t1`.`amount`) AS 'c2',
66
+ COUNT(`t1`.`amount`) AS 'c3',
67
+ MAX(`t1`.`amount`) AS 'c4',
68
+ MIN(`t1`.`amount`) AS 'c5',
69
+ SUM(`t1`.`amount`) AS 'c6'
70
+ FROM `patients` `t0`
71
+ LEFT OUTER JOIN `patient_payments` `t1` ON `t1`.`patient_id` = `t0`.`id`
72
+ GROUP BY `t0`.`id`, `t0`.`first`
@@ -0,0 +1,89 @@
1
+ model_name: Patients
2
+ query:
3
+ from: patients
4
+ fields:
5
+ - key_path: id
6
+ display: 'ID #'
7
+ - key_path: first
8
+ display: First Name
9
+ - key_path: patient_field_values.value
10
+ display: Date of Birth
11
+ aggregator: max
12
+ filters:
13
+ - key_path: patient_field_values.fields.section
14
+ value: demographics
15
+ - key_path: patient_field_values.fields.key
16
+ value: dob
17
+ - key_path: patient_field_values.value
18
+ display: Demographic Notes
19
+ aggregator: max
20
+ filters:
21
+ - key_path: patient_field_values.fields.section
22
+ value: demographics
23
+ - key_path: patient_field_values.fields.key
24
+ value: notes
25
+ - key_path: patient_field_values.value
26
+ display: 'Drivers License #'
27
+ aggregator: max
28
+ filters:
29
+ - key_path: patient_field_values.fields.section
30
+ value: demographics
31
+ - key_path: patient_field_values.fields.key
32
+ value: drivers_license
33
+ - key_path: patient_field_values.value
34
+ display: Contact Notes
35
+ aggregator: max
36
+ filters:
37
+ - key_path: patient_field_values.fields.section
38
+ value: contact
39
+ - key_path: patient_field_values.fields.key
40
+ value: notes
41
+
42
+ sqlite_readable: |+
43
+ SELECT
44
+ "patients"."id" AS 'ID #',
45
+ "patients"."first" AS 'First Name',
46
+ MAX(CASE WHEN "patient_field_values_fields"."section" = 'demographics' AND "patient_field_values_fields"."key" = 'dob' THEN "patient_field_values"."value" END) AS 'Date of Birth',
47
+ MAX(CASE WHEN "patient_field_values_fields"."section" = 'demographics' AND "patient_field_values_fields"."key" = 'notes' THEN "patient_field_values"."value" END) AS 'Demographic Notes',
48
+ MAX(CASE WHEN "patient_field_values_fields"."section" = 'demographics' AND "patient_field_values_fields"."key" = 'drivers_license' THEN "patient_field_values"."value" END) AS 'Drivers License #',
49
+ MAX(CASE WHEN "patient_field_values_fields"."section" = 'contact' AND "patient_field_values_fields"."key" = 'notes' THEN "patient_field_values"."value" END) AS 'Contact Notes'
50
+ FROM "patients" "patients"
51
+ LEFT OUTER JOIN "patient_field_values" "patient_field_values" ON "patient_field_values"."patient_id" = "patients"."id"
52
+ LEFT OUTER JOIN "fields" "patient_field_values_fields" ON "patient_field_values_fields"."id" = "patient_field_values"."field_id"
53
+ GROUP BY "patients"."id", "patients"."first"
54
+ sqlite_not_readable: |+
55
+ SELECT
56
+ "t0"."id" AS 'c0',
57
+ "t0"."first" AS 'c1',
58
+ MAX(CASE WHEN "t2"."section" = 'demographics' AND "t2"."key" = 'dob' THEN "t1"."value" END) AS 'c2',
59
+ MAX(CASE WHEN "t2"."section" = 'demographics' AND "t2"."key" = 'notes' THEN "t1"."value" END) AS 'c3',
60
+ MAX(CASE WHEN "t2"."section" = 'demographics' AND "t2"."key" = 'drivers_license' THEN "t1"."value" END) AS 'c4',
61
+ MAX(CASE WHEN "t2"."section" = 'contact' AND "t2"."key" = 'notes' THEN "t1"."value" END) AS 'c5'
62
+ FROM "patients" "t0"
63
+ LEFT OUTER JOIN "patient_field_values" "t1" ON "t1"."patient_id" = "t0"."id"
64
+ LEFT OUTER JOIN "fields" "t2" ON "t2"."id" = "t1"."field_id"
65
+ GROUP BY "t0"."id", "t0"."first"
66
+ mysql_readable: |+
67
+ SELECT
68
+ `patients`.`id` AS 'ID #',
69
+ `patients`.`first` AS 'First Name',
70
+ MAX(CASE WHEN `patient_field_values_fields`.`section` = 'demographics' AND `patient_field_values_fields`.`key` = 'dob' THEN `patient_field_values`.`value` END) AS 'Date of Birth',
71
+ MAX(CASE WHEN `patient_field_values_fields`.`section` = 'demographics' AND `patient_field_values_fields`.`key` = 'notes' THEN `patient_field_values`.`value` END) AS 'Demographic Notes',
72
+ MAX(CASE WHEN `patient_field_values_fields`.`section` = 'demographics' AND `patient_field_values_fields`.`key` = 'drivers_license' THEN `patient_field_values`.`value` END) AS 'Drivers License #',
73
+ MAX(CASE WHEN `patient_field_values_fields`.`section` = 'contact' AND `patient_field_values_fields`.`key` = 'notes' THEN `patient_field_values`.`value` END) AS 'Contact Notes'
74
+ FROM `patients` `patients`
75
+ LEFT OUTER JOIN `patient_field_values` `patient_field_values` ON `patient_field_values`.`patient_id` = `patients`.`id`
76
+ LEFT OUTER JOIN `fields` `patient_field_values_fields` ON `patient_field_values_fields`.`id` = `patient_field_values`.`field_id`
77
+ GROUP BY `patients`.`id`, `patients`.`first`
78
+ mysql_not_readable: |+
79
+ SELECT
80
+ `t0`.`id` AS 'c0',
81
+ `t0`.`first` AS 'c1',
82
+ MAX(CASE WHEN `t2`.`section` = 'demographics' AND `t2`.`key` = 'dob' THEN `t1`.`value` END) AS 'c2',
83
+ MAX(CASE WHEN `t2`.`section` = 'demographics' AND `t2`.`key` = 'notes' THEN `t1`.`value` END) AS 'c3',
84
+ MAX(CASE WHEN `t2`.`section` = 'demographics' AND `t2`.`key` = 'drivers_license' THEN `t1`.`value` END) AS 'c4',
85
+ MAX(CASE WHEN `t2`.`section` = 'contact' AND `t2`.`key` = 'notes' THEN `t1`.`value` END) AS 'c5'
86
+ FROM `patients` `t0`
87
+ LEFT OUTER JOIN `patient_field_values` `t1` ON `t1`.`patient_id` = `t0`.`id`
88
+ LEFT OUTER JOIN `fields` `t2` ON `t2`.`id` = `t1`.`field_id`
89
+ GROUP BY `t0`.`id`, `t0`.`first`
@@ -1,105 +1,133 @@
1
1
  Theaters, Members, and Movies:
2
- name: theaters
3
- table: theaters
4
- models:
5
- - name: members
6
- table: members
7
- constraints:
8
- - type: reference
9
- parent: id
10
- name: tid
11
- - type: reference
12
- parent: partition
13
- name: partition
14
- models:
15
- - name: demos
16
- table: demographics
17
- constraints:
18
- - type: reference
19
- parent: id
20
- name: member_id
21
- models:
22
- - name: phone_numbers
23
- table: phone_numbers
24
- constraints:
25
- - type: reference
26
- parent: id
27
- name: demographic_id
28
- - name: movies
29
- table: movies
30
- constraints:
31
- - type: reference
32
- parent: id
33
- name: member_id
34
- - name: favorite_comic_movies
35
- table: movies
36
- constraints:
37
- - type: reference
38
- parent: id
39
- name: member_id
40
- - type: static
41
- name: genre
42
- value: comic
43
- - name: favorite_mystery_movies
44
- table: movies
45
- constraints:
46
- - type: reference
47
- parent: id
48
- name: member_id
49
- - type: static
50
- name: genre
51
- value: mystery
52
- - name: favorite_comedy_movies
53
- table: movies
54
- constraints:
55
- - type: reference
56
- parent: id
57
- name: member_id
58
- - type: static
59
- name: genre
60
- value: comedy
2
+ theaters:
3
+ relationships:
4
+ members:
5
+ constraints:
6
+ - type: reference
7
+ parent: id
8
+ name: tid
9
+ - type: reference
10
+ parent: partition
11
+ name: partition
12
+ members:
13
+ relationships:
14
+ demos:
15
+ model: demographics
16
+ constraints:
17
+ - type: reference
18
+ parent: id
19
+ name: member_id
20
+ movies:
21
+ constraints:
22
+ - type: reference
23
+ parent: id
24
+ name: member_id
25
+ favorite_comic_movies:
26
+ model: movies
27
+ constraints:
28
+ - type: reference
29
+ parent: id
30
+ name: member_id
31
+ - type: static
32
+ name: genre
33
+ value: comic
34
+ favorite_mystery_movies:
35
+ model: movies
36
+ constraints:
37
+ - type: reference
38
+ parent: id
39
+ name: member_id
40
+ - type: static
41
+ name: genre
42
+ value: mystery
43
+ favorite_comedy_movies:
44
+ model: movies
45
+ constraints:
46
+ - type: reference
47
+ parent: id
48
+ name: member_id
49
+ - type: static
50
+ name: genre
51
+ value: comedy
52
+ demographics:
53
+ table: demographics
54
+ relationships:
55
+ phone_numbers:
56
+ constraints:
57
+ - type: reference
58
+ parent: id
59
+ name: demographic_id
60
+ phone_numbers:
61
+ movies:
62
+
61
63
  Reverse Polymorphic Example:
62
64
  # In this example, an animal has a toy, but that toy is either a dog or cat toy, depending on
63
65
  # the type of the animal. So for this to work in this direction, static constraints pointed
64
66
  # at the parent (animals) is needed.
65
- name: animals
66
- models:
67
- - name: dog_toy
68
- table: dog_toys
67
+ animals:
68
+ relationships:
69
+ dog_toy:
70
+ model: dog_toys
69
71
  constraints:
70
72
  - parent: toy_id
71
73
  name: id
72
74
  - type: static
73
75
  parent: type
74
76
  value: Dog
75
- - name: cat_toy
76
- table: cat_toys
77
+ cat_toy:
78
+ model: cat_toys
77
79
  constraints:
78
80
  - parent: toy_id
79
81
  name: id
80
82
  - type: static
81
83
  parent: type
82
84
  value: Cat
85
+ dog_toys:
86
+ cat_toys:
83
87
 
84
88
  Partitioner Example 1:
85
- name: dogs
86
- table: animals
87
- partitioners:
88
- - name: type
89
- value: Dog
90
- - name: deleted
91
- value: false
89
+ dogs:
90
+ table: animals
91
+ partitioners:
92
+ - name: type
93
+ value: Dog
94
+ - name: deleted
95
+ value: false
92
96
 
93
97
  Partitioner Example 2:
94
- name: owners
95
- models:
96
- - name: dogs
97
- table: animals
98
- constraints:
99
- - name: owner_id
100
- parent: id
101
- partitioners:
102
- - name: type
103
- value: Dog
104
- - name: deleted
105
- value: false
98
+ owners:
99
+ relationships:
100
+ dogs:
101
+ constraints:
102
+ - name: owner_id
103
+ parent: id
104
+ dogs:
105
+ table: animals
106
+ partitioners:
107
+ - name: type
108
+ value: Dog
109
+ - name: deleted
110
+ value: false
111
+
112
+ Patients:
113
+ patients:
114
+ relationships:
115
+ patient_payments:
116
+ constraints:
117
+ - type: reference
118
+ parent: id
119
+ name: patient_id
120
+ patient_field_values:
121
+ constraints:
122
+ - type: reference
123
+ parent: id
124
+ name: patient_id
125
+ patient_field_values:
126
+ relationships:
127
+ fields:
128
+ constraints:
129
+ - type: reference
130
+ parent: field_id
131
+ name: id
132
+ patient_payments:
133
+ fields: