@kaspernj/api-maker 1.0.267 → 1.0.269

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.
package/package.json CHANGED
@@ -16,7 +16,7 @@
16
16
  ]
17
17
  },
18
18
  "name": "@kaspernj/api-maker",
19
- "version": "1.0.267",
19
+ "version": "1.0.269",
20
20
  "type": "module",
21
21
  "description": "",
22
22
  "main": "index.js",
@@ -52,6 +52,7 @@
52
52
  "on-location-changed": ">= 1.0.7",
53
53
  "qs": ">= 6.9.3",
54
54
  "replaceall": ">= 0.1.6",
55
+ "spark-md5": "^3.0.2",
55
56
  "strftime": ">= 0.10.0",
56
57
  "uniqunize": "^1.0.1",
57
58
  "wake-event": ">= 0.0.1"
@@ -1,5 +1,6 @@
1
1
  import Attribute from "./base-model/attribute.mjs"
2
2
  import AttributeNotLoadedError from "./attribute-not-loaded-error.mjs"
3
+ import CacheKeyGenerator from "./cache-key-generator.mjs"
3
4
  import Collection from "./collection.mjs"
4
5
  import CommandsPool from "./commands-pool.mjs"
5
6
  import Config from "./config.mjs"
@@ -146,6 +147,7 @@ export default class BaseModel {
146
147
  this.changes = {}
147
148
  this.newRecord = args.isNewRecord
148
149
  this.relationshipsCache = {}
150
+ this.relationships = {}
149
151
 
150
152
  if (args && args.data && args.data.a) {
151
153
  this._readModelDataFromArgs(args)
@@ -207,6 +209,7 @@ export default class BaseModel {
207
209
 
208
210
  clone.abilities = {...this.abilities}
209
211
  clone.modelData = {...this.modelData}
212
+ clone.relationships = {...this.relationships}
210
213
  clone.relationshipsCache = {...this.relationshipsCache}
211
214
 
212
215
  return clone
@@ -237,6 +240,12 @@ export default class BaseModel {
237
240
  }
238
241
  }
239
242
 
243
+ fullCacheKey() {
244
+ const cacheKeyGenerator = new CacheKeyGenerator(this)
245
+
246
+ return cacheKeyGenerator.cacheKey()
247
+ }
248
+
240
249
  static all () {
241
250
  return this.ransack()
242
251
  }
@@ -385,6 +394,12 @@ export default class BaseModel {
385
394
  return false
386
395
  }
387
396
 
397
+ isAssociationPresent (associationName) {
398
+ if (this.isAssociationLoaded(associationName)) return true
399
+ if (associationName in this.relationships) return true
400
+ return false
401
+ }
402
+
388
403
  static parseValidationErrors ({error, model, options}) {
389
404
  if (!(error instanceof ValidationError)) return
390
405
  if (!error.args.response.validation_errors) return
@@ -489,6 +504,7 @@ export default class BaseModel {
489
504
 
490
505
  setNewModel (model) {
491
506
  this.setNewModelData(model)
507
+ this.relationships = digg(model, "relationships")
492
508
  this.relationshipsCache = digg(model, "relationshipsCache")
493
509
  }
494
510
 
@@ -643,7 +659,8 @@ export default class BaseModel {
643
659
  {
644
660
  args: {
645
661
  query_params: this.collection && this.collection.params(),
646
- save: objectData
662
+ save: objectData,
663
+ simple_model_errors: options?.simpleModelErrors
647
664
  },
648
665
  command: `${this.modelClassData().collectionName}-update`,
649
666
  collectionName: this.modelClassData().collectionName,
@@ -697,6 +714,7 @@ export default class BaseModel {
697
714
 
698
715
  preloadRelationship (relationshipName, model) {
699
716
  this.relationshipsCache[BaseModel.snakeCase(relationshipName)] = model
717
+ this.relationships[BaseModel.snakeCase(relationshipName)] = model
700
718
  }
701
719
 
702
720
  uniqueKey () {
@@ -792,7 +810,9 @@ export default class BaseModel {
792
810
  }
793
811
 
794
812
  async _loadBelongsToReflection (args, queryArgs = {}) {
795
- if (args.reflectionName in this.relationshipsCache) {
813
+ if (args.reflectionName in this.relationships) {
814
+ return this.relationships[args.reflectionName]
815
+ } else if (args.reflectionName in this.relationshipsCache) {
796
816
  return this.relationshipsCache[args.reflectionName]
797
817
  } else {
798
818
  const collection = new Collection(args, queryArgs)
@@ -803,21 +823,24 @@ export default class BaseModel {
803
823
  }
804
824
 
805
825
  _readBelongsToReflection ({reflectionName}) {
806
- if (!(reflectionName in this.relationshipsCache)) {
807
- if (this.isNewRecord())
808
- return null
826
+ if (reflectionName in this.relationships) {
827
+ return this.relationships[reflectionName]
828
+ } else if (reflectionName in this.relationshipsCache) {
829
+ return this.relationshipsCache[reflectionName]
830
+ }
809
831
 
810
- const loadedRelationships = Object.keys(this.relationshipsCache)
811
- const modelClassName = digg(this.modelClassData(), "name")
832
+ if (this.isNewRecord()) return null
812
833
 
813
- throw new NotLoadedError(`${modelClassName}#${reflectionName} hasn't been loaded yet. Only these were loaded: ${loadedRelationships.join(", ")}`)
814
- }
834
+ const loadedRelationships = Object.keys(this.relationshipsCache)
835
+ const modelClassName = digg(this.modelClassData(), "name")
815
836
 
816
- return this.relationshipsCache[reflectionName]
837
+ throw new NotLoadedError(`${modelClassName}#${reflectionName} hasn't been loaded yet. Only these were loaded: ${loadedRelationships.join(", ")}`)
817
838
  }
818
839
 
819
840
  async _loadHasManyReflection (args, queryArgs = {}) {
820
- if (args.reflectionName in this.relationshipsCache) {
841
+ if (args.reflectionName in this.relationships) {
842
+ return this.relationships[args.reflectionName]
843
+ } else if (args.reflectionName in this.relationshipsCache) {
821
844
  return this.relationshipsCache[args.reflectionName]
822
845
  }
823
846
 
@@ -830,7 +853,9 @@ export default class BaseModel {
830
853
  }
831
854
 
832
855
  async _loadHasOneReflection (args, queryArgs = {}) {
833
- if (args.reflectionName in this.relationshipsCache) {
856
+ if (args.reflectionName in this.relationships) {
857
+ return this.relationships[args.reflectionName]
858
+ } else if (args.reflectionName in this.relationshipsCache) {
834
859
  return this.relationshipsCache[args.reflectionName]
835
860
  } else {
836
861
  const collection = new Collection(args, queryArgs)
@@ -843,17 +868,20 @@ export default class BaseModel {
843
868
  }
844
869
 
845
870
  _readHasOneReflection ({reflectionName}) {
846
- if (!(reflectionName in this.relationshipsCache)) {
847
- if (this.isNewRecord())
848
- return null
849
-
850
- const loadedRelationships = Object.keys(this.relationshipsCache)
851
- const modelClassName = digg(this.modelClassData(), "name")
871
+ if (reflectionName in this.relationships) {
872
+ return this.relationships[reflectionName]
873
+ } else if (reflectionName in this.relationshipsCache) {
874
+ return this.relationshipsCache[reflectionName]
875
+ }
852
876
 
853
- throw new NotLoadedError(`${modelClassName}#${reflectionName} hasn't been loaded yet. Only these were loaded: ${loadedRelationships.join(", ")}`)
877
+ if (this.isNewRecord()) {
878
+ return null
854
879
  }
855
880
 
856
- return this.relationshipsCache[reflectionName]
881
+ const loadedRelationships = Object.keys(this.relationshipsCache)
882
+ const modelClassName = digg(this.modelClassData(), "name")
883
+
884
+ throw new NotLoadedError(`${modelClassName}#${reflectionName} hasn't been loaded yet. Only these were loaded: ${loadedRelationships.join(", ")}`)
857
885
  }
858
886
 
859
887
  _readModelDataFromArgs (args) {
@@ -893,19 +921,22 @@ export default class BaseModel {
893
921
 
894
922
  if (!relationshipData) {
895
923
  this.relationshipsCache[relationshipName] = null
924
+ this.relationships[relationshipName] = null
896
925
  } else if (Array.isArray(relationshipData)) {
897
- const result = []
926
+ this.relationshipsCache[relationshipName] = []
927
+ this.relationships[relationshipName] = []
898
928
 
899
929
  for (const relationshipId of relationshipData) {
900
930
  const model = preloaded.getModel(relationshipType, relationshipId)
901
931
 
902
- result.push(model)
932
+ this.relationshipsCache[relationshipName].push(model)
933
+ this.relationships[relationshipName].push(model)
903
934
  }
904
-
905
- this.relationshipsCache[relationshipName] = result
906
935
  } else {
907
936
  const model = preloaded.getModel(relationshipType, relationshipData)
937
+
908
938
  this.relationshipsCache[relationshipName] = model
939
+ this.relationships[relationshipName] = model
909
940
  }
910
941
  }
911
942
  }
@@ -0,0 +1,65 @@
1
+ import SparkMD5 from "spark-md5"
2
+
3
+ export default class CacheKeyGenerator {
4
+ constructor(model) {
5
+ this.allModels = [model]
6
+ this.readModels = {}
7
+ this.recordModelType(model.modelClassData().name)
8
+ this.recordModel(model.modelClassData().name, model)
9
+ this.fillModels(model)
10
+ }
11
+
12
+ recordModelType(relationshipType) {
13
+ if (!(relationshipType in this.readModels)) {
14
+ this.readModels[relationshipType] = {}
15
+ }
16
+ }
17
+
18
+ recordModel(relationshipType, model) {
19
+ this.allModels.push(model)
20
+ this.readModels[relationshipType][model.id() || model.uniqueKey()] = true
21
+ }
22
+
23
+ isModelRecorded(relationshipType, model) {
24
+ if (model.id() in this.readModels[relationshipType]) {
25
+ return true
26
+ }
27
+ }
28
+
29
+ fillModels(model) {
30
+ for (const relationshipType in model.relationships) {
31
+ this.recordModelType(relationshipType)
32
+
33
+ for (const anotherModel of model.relationships[relationshipType]) {
34
+ if (this.isModelRecorded(relationshipType, anotherModel)) {
35
+ continue
36
+ }
37
+
38
+ this.recordModel(relationshipType, anotherModel)
39
+ this.fillModels(anotherModel)
40
+ }
41
+ }
42
+ }
43
+
44
+ cacheKey() {
45
+ const md5 = new SparkMD5()
46
+
47
+ for (const model of this.allModels) {
48
+ md5.append("-model-")
49
+ md5.append(model.modelClassData().name)
50
+ md5.append("-unique-key-")
51
+ md5.append(model.id() || model.uniqueKey())
52
+ md5.append("-attributes-")
53
+
54
+ const attributes = model.attributes()
55
+
56
+ for (const attributeName in attributes) {
57
+ md5.append(attributeName)
58
+ md5.append("-attribute-")
59
+ md5.append(`${model.readAttributeUnderscore(attributeName)}`)
60
+ }
61
+ }
62
+
63
+ return md5.end()
64
+ }
65
+ }
@@ -87,7 +87,7 @@ export default class ApiMakerCollection {
87
87
  return this._merge({limit: amount})
88
88
  }
89
89
 
90
- loaded () {
90
+ preloaded () {
91
91
  if (!(this.args.reflectionName in this.args.model.relationshipsCache)) {
92
92
  throw new Error(`${this.args.reflectionName} hasnt been loaded yet`)
93
93
  }
@@ -95,6 +95,35 @@ export default class ApiMakerCollection {
95
95
  return this.args.model.relationshipsCache[this.args.reflectionName]
96
96
  }
97
97
 
98
+ loaded () {
99
+ if (this.args.reflectionName in this.args.model.relationships) {
100
+ return this.args.model.relationships[this.args.reflectionName]
101
+ } else if (this.args.reflectionName in this.args.model.relationshipsCache) {
102
+ return this.args.model.relationshipsCache[this.args.reflectionName]
103
+ } else {
104
+ throw new Error(`${this.args.reflectionName} hasnt been loaded yet`)
105
+ }
106
+ }
107
+
108
+ // Replaces the relationships with the given new collection.
109
+ set(newCollection) {
110
+ this.args.model.relationships[this.args.reflectionName] = newCollection
111
+ }
112
+
113
+ // Pushes another model onto the given collection.
114
+ push(newModel) {
115
+ if (!(this.args.reflectionName in this.args.model.relationships)) {
116
+ this.args.model.relationships[this.args.reflectionName] = []
117
+ }
118
+
119
+ this.args.model.relationships[this.args.reflectionName].push(newModel)
120
+ }
121
+
122
+ // Array shortcuts
123
+ find = (...args) => this.loaded().find(...args)
124
+ forEach = (...args) => this.loaded().forEach(...args)
125
+ map = (...args) => this.loaded().map(...args)
126
+
98
127
  preload (preloadValue) {
99
128
  return this._merge({preload: preloadValue})
100
129
  }
@@ -60,7 +60,6 @@ class ReflectionElement extends React.PureComponent {
60
60
  className="reflection-element"
61
61
  data-model-class={currentModelClass.modelClassData().name}
62
62
  data-reflection-name={reflection.name()}
63
- key={reflection.name()}
64
63
  onClick={digg(this, "onReflectionClicked")}
65
64
  >
66
65
  {currentModelClass.humanAttributeName(reflection.name())}
@@ -75,6 +74,42 @@ class ReflectionElement extends React.PureComponent {
75
74
  }
76
75
  }
77
76
 
77
+ class ScopeElement extends React.PureComponent {
78
+ static defaultProps = {
79
+ active: false
80
+ }
81
+
82
+ static propTypes = {
83
+ active: PropTypes.bool.isRequired,
84
+ scope: PropTypes.object.isRequired
85
+ }
86
+
87
+ render() {
88
+ const {active, scope} = this.props
89
+ const style = {}
90
+
91
+ if (active) style.fontWeight = "bold"
92
+
93
+ return (
94
+ <div
95
+ className="scope-element"
96
+ key={scope.name()}
97
+ onClick={digg(this, "onScopeClicked")}
98
+ style={style}
99
+ >
100
+ {scope.name()}
101
+ </div>
102
+ )
103
+ }
104
+
105
+ onScopeClicked = (e) => {
106
+ e.preventDefault()
107
+
108
+ this.props.onScopeClicked({scope: this.props.scope})
109
+ }
110
+ }
111
+
112
+
78
113
  export default class ApiMakerTableFiltersFilterForm extends React.PureComponent {
79
114
  static propTypes = PropTypesExact({
80
115
  filter: PropTypes.object,
@@ -88,6 +123,7 @@ export default class ApiMakerTableFiltersFilterForm extends React.PureComponent
88
123
  path: this.props.filter.p || [],
89
124
  predicate: undefined,
90
125
  predicates: undefined,
126
+ scope: this.props.filter.sc,
91
127
  value: this.props.filter.v
92
128
  })
93
129
  valueInputRef = React.createRef()
@@ -114,7 +150,14 @@ export default class ApiMakerTableFiltersFilterForm extends React.PureComponent
114
150
  render() {
115
151
  const {valueInputRef} = digs(this, "valueInputRef")
116
152
  const currentModelClass = this.currentModelClass()
117
- const {attribute, predicate, predicates, value} = digs(this.shape, "attribute", "predicate", "predicates", "value")
153
+ const {attribute, predicate, predicates, scope, value} = digs(this.shape, "attribute", "predicate", "scope", "predicates", "value")
154
+ let submitEnabled = false
155
+
156
+ if (attribute && predicate) {
157
+ submitEnabled = true
158
+ } else if (scope) {
159
+ submitEnabled = true
160
+ }
118
161
 
119
162
  return (
120
163
  <div className="api-maker--table--filters--filter-form">
@@ -153,13 +196,16 @@ export default class ApiMakerTableFiltersFilterForm extends React.PureComponent
153
196
  />
154
197
  )}
155
198
  {currentModelClass.ransackableScopes().map((scope) =>
156
- <div key={scope.name()}>
157
- {scope.name()}
158
- </div>
199
+ <ScopeElement
200
+ active={scope.name() == this.shape.scope?.name()}
201
+ key={scope.name()}
202
+ scope={scope}
203
+ onScopeClicked={digg(this, "onScopeClicked")}
204
+ />
159
205
  )}
160
206
  </div>
161
207
  <div>
162
- {predicates &&
208
+ {predicates && !this.shape.scope &&
163
209
  <Select
164
210
  className="predicate-select"
165
211
  defaultValue={predicate?.name}
@@ -170,13 +216,13 @@ export default class ApiMakerTableFiltersFilterForm extends React.PureComponent
170
216
  }
171
217
  </div>
172
218
  <div>
173
- {attribute && predicate &&
219
+ {((attribute && predicate) || scope) &&
174
220
  <Input className="value-input" defaultValue={value} inputRef={valueInputRef} />
175
221
  }
176
222
  </div>
177
223
  </div>
178
224
  <div>
179
- <button className="apply-filter-button" disabled={!attribute || !predicate}>
225
+ <button className="apply-filter-button" disabled={!submitEnabled}>
180
226
  {I18n.t("js.api_maker.table.filters.relationship_select.apply", {defaultValue: "Apply"})}
181
227
  </button>
182
228
  </div>
@@ -238,7 +284,11 @@ export default class ApiMakerTableFiltersFilterForm extends React.PureComponent
238
284
  }
239
285
 
240
286
  onAttributeClicked = ({attribute}) => {
241
- this.shape.set({attribute})
287
+ this.shape.set({
288
+ attribute,
289
+ predicate: undefined,
290
+ scope: undefined
291
+ })
242
292
  }
243
293
 
244
294
  onPredicateChanged = (e) => {
@@ -253,27 +303,45 @@ export default class ApiMakerTableFiltersFilterForm extends React.PureComponent
253
303
 
254
304
  this.shape.set({
255
305
  attribute: undefined,
256
- path: newPath
306
+ path: newPath,
307
+ predicate: undefined
257
308
  })
258
309
 
259
310
  this.props.onPathChanged
260
311
  }
261
312
 
313
+ onScopeClicked = ({scope}) => {
314
+ this.shape.set({
315
+ attribute: undefined,
316
+ scope
317
+ })
318
+ }
319
+
262
320
  onSubmit = (e) => {
263
321
  e.preventDefault()
264
322
 
265
323
  const {filter, querySearchName} = digs(this.props, "filter", "querySearchName")
266
- const {attribute, path, predicate} = digs(this.shape, "attribute", "path", "predicate")
324
+ const {attribute, path, predicate, scope} = digs(this.shape, "attribute", "path", "predicate", "scope")
267
325
  const {filterIndex} = digs(filter, "filterIndex")
268
326
  const searchParams = Params.parse()[querySearchName] || {}
269
327
  const value = digg(this, "valueInputRef", "current", "value")
270
-
271
- searchParams[filterIndex] = JSON.stringify({
272
- a: attribute.name(),
328
+ const newSearchParams = {
273
329
  p: path,
274
- pre: digg(predicate, "name"),
275
330
  v: value
276
- })
331
+ }
332
+
333
+ if (attribute) {
334
+ newSearchParams.a = attribute.name()
335
+ newSearchParams.pre = digg(predicate, "name")
336
+ } else if (scope) {
337
+ newSearchParams.sc = inflection.underscore(scope.name())
338
+ } else {
339
+ throw new Error("Dont know if should search for attribute or scope?")
340
+ }
341
+
342
+ console.log({newSearchParams})
343
+
344
+ searchParams[filterIndex] = JSON.stringify(newSearchParams)
277
345
 
278
346
  const newParams = {}
279
347
 
@@ -17,7 +17,8 @@ class ApiMakerTableFilter extends React.PureComponent {
17
17
  }
18
18
 
19
19
  render() {
20
- const {a, p, pre, v} = digs(this.props, "a", "p", "pre", "v")
20
+ const {p, v} = digs(this.props, "p", "v")
21
+ const {a, pre, sc} = this.props
21
22
 
22
23
  return (
23
24
  <div style={{display: "inline-block", backgroundColor: "grey", padding: "10px 6px"}}>
@@ -25,7 +26,7 @@ class ApiMakerTableFilter extends React.PureComponent {
25
26
  {p.length > 0 &&
26
27
  `${p.join(".")}.`
27
28
  }
28
- {a} {pre} {v}
29
+ {a} {sc} {pre} {v}
29
30
  </span>
30
31
  <span>
31
32
  <a className="remove-filter-button" href="#" onClick={digg(this, "onRemoveFilterClicked")}>