api_maker 0.0.1

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 (103) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +476 -0
  4. data/Rakefile +27 -0
  5. data/app/channels/api_maker/subscriptions_channel.rb +80 -0
  6. data/app/controllers/api_maker/base_controller.rb +32 -0
  7. data/app/controllers/api_maker/commands_controller.rb +26 -0
  8. data/app/controllers/api_maker/devise_controller.rb +60 -0
  9. data/app/controllers/api_maker/session_statuses_controller.rb +33 -0
  10. data/app/services/api_maker/application_service.rb +7 -0
  11. data/app/services/api_maker/collection_command_service.rb +24 -0
  12. data/app/services/api_maker/command_response.rb +67 -0
  13. data/app/services/api_maker/command_service.rb +31 -0
  14. data/app/services/api_maker/create_command.rb +62 -0
  15. data/app/services/api_maker/create_command_service.rb +18 -0
  16. data/app/services/api_maker/destroy_command.rb +39 -0
  17. data/app/services/api_maker/destroy_command_service.rb +22 -0
  18. data/app/services/api_maker/generate_react_native_api_service.rb +61 -0
  19. data/app/services/api_maker/index_command.rb +96 -0
  20. data/app/services/api_maker/index_command_service.rb +22 -0
  21. data/app/services/api_maker/js_method_namer_service.rb +11 -0
  22. data/app/services/api_maker/member_command_service.rb +25 -0
  23. data/app/services/api_maker/model_content_generator_service.rb +108 -0
  24. data/app/services/api_maker/models_finder_service.rb +22 -0
  25. data/app/services/api_maker/models_generator_service.rb +104 -0
  26. data/app/services/api_maker/update_command.rb +43 -0
  27. data/app/services/api_maker/update_command_service.rb +21 -0
  28. data/app/services/api_maker/valid_command.rb +35 -0
  29. data/app/services/api_maker/valid_command_service.rb +21 -0
  30. data/app/views/api_maker/_data.html.erb +15 -0
  31. data/config/rails_best_practices.yml +55 -0
  32. data/config/routes.rb +7 -0
  33. data/lib/api_maker.rb +36 -0
  34. data/lib/api_maker/ability.rb +39 -0
  35. data/lib/api_maker/ability_loader.rb +21 -0
  36. data/lib/api_maker/action_controller_base_extensions.rb +5 -0
  37. data/lib/api_maker/base_command.rb +81 -0
  38. data/lib/api_maker/base_resource.rb +78 -0
  39. data/lib/api_maker/collection_serializer.rb +69 -0
  40. data/lib/api_maker/command_spec_helper.rb +57 -0
  41. data/lib/api_maker/configuration.rb +34 -0
  42. data/lib/api_maker/engine.rb +5 -0
  43. data/lib/api_maker/individual_command.rb +37 -0
  44. data/lib/api_maker/javascript/api.js +92 -0
  45. data/lib/api_maker/javascript/base-model.js +543 -0
  46. data/lib/api_maker/javascript/bootstrap/attribute-row.jsx +16 -0
  47. data/lib/api_maker/javascript/bootstrap/attribute-rows.jsx +47 -0
  48. data/lib/api_maker/javascript/bootstrap/card.jsx +79 -0
  49. data/lib/api_maker/javascript/bootstrap/checkbox.jsx +127 -0
  50. data/lib/api_maker/javascript/bootstrap/checkboxes.jsx +105 -0
  51. data/lib/api_maker/javascript/bootstrap/live-table.jsx +168 -0
  52. data/lib/api_maker/javascript/bootstrap/money-input.jsx +136 -0
  53. data/lib/api_maker/javascript/bootstrap/radio-buttons.jsx +80 -0
  54. data/lib/api_maker/javascript/bootstrap/select.jsx +168 -0
  55. data/lib/api_maker/javascript/bootstrap/string-input.jsx +203 -0
  56. data/lib/api_maker/javascript/cable-connection-pool.js +169 -0
  57. data/lib/api_maker/javascript/cable-subscription-pool.js +111 -0
  58. data/lib/api_maker/javascript/cable-subscription.js +33 -0
  59. data/lib/api_maker/javascript/collection.js +186 -0
  60. data/lib/api_maker/javascript/commands-pool.js +123 -0
  61. data/lib/api_maker/javascript/custom-error.js +14 -0
  62. data/lib/api_maker/javascript/deserializer.js +35 -0
  63. data/lib/api_maker/javascript/devise.js.erb +113 -0
  64. data/lib/api_maker/javascript/error-logger.js +119 -0
  65. data/lib/api_maker/javascript/event-connection.jsx +24 -0
  66. data/lib/api_maker/javascript/event-created.jsx +26 -0
  67. data/lib/api_maker/javascript/event-destroyed.jsx +26 -0
  68. data/lib/api_maker/javascript/event-emitter-listener.jsx +32 -0
  69. data/lib/api_maker/javascript/event-listener.jsx +41 -0
  70. data/lib/api_maker/javascript/event-updated.jsx +26 -0
  71. data/lib/api_maker/javascript/form-data-to-object.js +70 -0
  72. data/lib/api_maker/javascript/included.js +39 -0
  73. data/lib/api_maker/javascript/key-value-store.js +47 -0
  74. data/lib/api_maker/javascript/logger.js +23 -0
  75. data/lib/api_maker/javascript/model-name.js +21 -0
  76. data/lib/api_maker/javascript/model-template.js.erb +110 -0
  77. data/lib/api_maker/javascript/models-response-reader.js +43 -0
  78. data/lib/api_maker/javascript/paginate.jsx +128 -0
  79. data/lib/api_maker/javascript/params.js +68 -0
  80. data/lib/api_maker/javascript/resource-route.jsx +75 -0
  81. data/lib/api_maker/javascript/resource-routes.jsx +36 -0
  82. data/lib/api_maker/javascript/result.js +25 -0
  83. data/lib/api_maker/javascript/session-status-updater.js +113 -0
  84. data/lib/api_maker/javascript/sort-link.jsx +88 -0
  85. data/lib/api_maker/javascript/updated-attribute.jsx +60 -0
  86. data/lib/api_maker/loader.rb +14 -0
  87. data/lib/api_maker/memory_storage.rb +65 -0
  88. data/lib/api_maker/model_extensions.rb +96 -0
  89. data/lib/api_maker/permitted_params_argument.rb +12 -0
  90. data/lib/api_maker/preloader.rb +91 -0
  91. data/lib/api_maker/preloader_belongs_to.rb +58 -0
  92. data/lib/api_maker/preloader_has_many.rb +69 -0
  93. data/lib/api_maker/preloader_has_one.rb +70 -0
  94. data/lib/api_maker/preloader_through.rb +101 -0
  95. data/lib/api_maker/railtie.rb +14 -0
  96. data/lib/api_maker/relationship_includer.rb +42 -0
  97. data/lib/api_maker/resource_routing.rb +8 -0
  98. data/lib/api_maker/result_parser.rb +50 -0
  99. data/lib/api_maker/serializer.rb +86 -0
  100. data/lib/api_maker/spec_helper.rb +100 -0
  101. data/lib/api_maker/version.rb +3 -0
  102. data/lib/tasks/api_maker_tasks.rake +5 -0
  103. metadata +581 -0
@@ -0,0 +1,69 @@
1
+ class ApiMaker::CollectionSerializer
2
+ def initialize(ability: nil, args: {}, collection:, include_param:, select: nil)
3
+ @ability = ability || ApiMaker::Ability.new(args: args)
4
+ @args = args
5
+ @collection = collection
6
+ @include_param = include_param
7
+ @select = select
8
+ end
9
+
10
+ def result
11
+ @result ||= begin
12
+ data = {
13
+ data: {},
14
+ included: {}
15
+ }
16
+
17
+ records = {}
18
+
19
+ ApiMaker::Configuration.profile("CollectionSerializer result collection map") do
20
+ @collection.map do |model|
21
+ serializer = ApiMaker::Serializer.new(ability: @ability, args: @args, model: model, select: select_for(model))
22
+ resource = serializer.resource
23
+ collection_name = resource.collection_name
24
+ id = model.id
25
+
26
+ data.fetch(:included)[collection_name] ||= {}
27
+ data.fetch(:included)[collection_name][id] ||= serializer
28
+
29
+ data.fetch(:data)[collection_name] ||= []
30
+ data.fetch(:data)[collection_name] << id
31
+
32
+ records[collection_name] ||= {}
33
+ records[collection_name][id] ||= serializer
34
+ end
35
+ end
36
+
37
+ preload_collection(data, records) if @collection.length.positive?
38
+
39
+ data
40
+ end
41
+ end
42
+
43
+ def as_json(options = nil)
44
+ result.as_json(options)
45
+ end
46
+
47
+ def preload_collection(data, records)
48
+ ApiMaker::Configuration.profile("CollectionSerializer result preloading") do
49
+ preloader = ApiMaker::Preloader.new(
50
+ ability: @ability,
51
+ args: @args,
52
+ collection: @collection,
53
+ data: data,
54
+ include_param: @include_param,
55
+ records: records,
56
+ select: @select
57
+ )
58
+ preloader.fill_data
59
+ end
60
+ end
61
+
62
+ def select_for(model)
63
+ @select&.dig(model.class)
64
+ end
65
+
66
+ def to_json(options = nil)
67
+ JSON.generate(as_json(options))
68
+ end
69
+ end
@@ -0,0 +1,57 @@
1
+ class ApiMaker::CommandSpecHelper
2
+ attr_reader :commands, :command_class, :collection
3
+
4
+ def initialize(command:, collection: nil, controller: nil)
5
+ @collection = collection
6
+ @command_class = command
7
+ @commands = {}
8
+ @controller = controller || double
9
+ end
10
+
11
+ def add_command(args: {}, primary_key: nil)
12
+ id = commands.length + 1
13
+
14
+ commands[id] = {
15
+ args: ActionController::Parameters.new(args),
16
+ id: id,
17
+ primary_key: primary_key
18
+ }
19
+
20
+ AddedCommand.new(id, response)
21
+ end
22
+
23
+ def command
24
+ @command ||= command_class.new(
25
+ ability: controller.__send__(:current_ability),
26
+ args: controller.__send__(:api_maker_args),
27
+ collection: collection,
28
+ commands: commands,
29
+ command_response: response,
30
+ controller: controller
31
+ )
32
+ end
33
+
34
+ def controller
35
+ @controller ||= double(current_user: user)
36
+ end
37
+
38
+ def execute!
39
+ command.execute!
40
+ ServicePattern::Response.new(success: true)
41
+ end
42
+
43
+ def response
44
+ @response ||= ApiMaker::CommandResponse.new(controller: @controller)
45
+ end
46
+
47
+ class AddedCommand
48
+ def initialize(id, response)
49
+ @id = id
50
+ @response = response
51
+ end
52
+
53
+ def result
54
+ @result ||= @response.result.fetch(@id).fetch(:data)
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,34 @@
1
+ class ApiMaker::Configuration
2
+ attr_accessor :profiling, :react_native_path, :threadding
3
+
4
+ def self.current
5
+ @current ||= ApiMaker::Configuration.new
6
+ end
7
+
8
+ def self.configure
9
+ yield ApiMaker::Configuration.current
10
+ end
11
+
12
+ def self.profile(name, &blk)
13
+ if ApiMaker::Configuration.current.profiling
14
+ Rack::MiniProfiler.step("AM #{name}", &blk)
15
+ else
16
+ yield
17
+ end
18
+ end
19
+
20
+ def initialize
21
+ @on_error = []
22
+ @threadding = true
23
+ end
24
+
25
+ def on_error(&blk)
26
+ @on_error << blk
27
+ end
28
+
29
+ def report_error(error)
30
+ @on_error.each do |on_error|
31
+ on_error.call(error)
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,5 @@
1
+ module ApiMaker; end
2
+
3
+ class ApiMaker::Engine < ::Rails::Engine
4
+ isolate_namespace ApiMaker
5
+ end
@@ -0,0 +1,37 @@
1
+ class ApiMaker::IndividualCommand
2
+ attr_reader :args, :command, :id
3
+
4
+ def initialize(id:, args:, collection:, command:, primary_key: nil, response:)
5
+ @id = id
6
+ @args = args
7
+ @collection = collection
8
+ @command = command
9
+ @primary_key = primary_key
10
+ @response = response
11
+ end
12
+
13
+ def error(data = nil)
14
+ @response.error_for_command(@id, data)
15
+ end
16
+
17
+ def fail(data = nil)
18
+ @response.fail_for_command(@id, data)
19
+ end
20
+
21
+ def model
22
+ raise "Collection wasn't set" unless @collection
23
+
24
+ @model ||= @collection.find { |model| model.id.to_s == @primary_key.to_s }
25
+ raise "Couldn't find model by that ID: #{@primary_key}" unless @model
26
+
27
+ @model
28
+ end
29
+
30
+ def model_id
31
+ @primary_key
32
+ end
33
+
34
+ def result(data = nil)
35
+ @response.result_for_command(@id, ApiMaker::ResultParser.parse(data, controller: @response.controller))
36
+ end
37
+ end
@@ -0,0 +1,92 @@
1
+ import CustomError from "./custom-error"
2
+ import qs from "qs"
3
+
4
+ export default class {
5
+ static get(path, data = null) {
6
+ return this.requestLocal({"path": path, "pathParams": data, "method": "GET"})
7
+ }
8
+
9
+ static delete(path, data = null) {
10
+ return this.requestLocal({"path": path, "pathParams": data, "method": "DELETE"})
11
+ }
12
+
13
+ static patch(path, data = {}) {
14
+ return this.requestLocal({"path": path, "data": data, "method": "PATCH"})
15
+ }
16
+
17
+ static post(path, data = {}) {
18
+ return this.requestLocal({"path": path, "data": data, "method": "POST"})
19
+ }
20
+
21
+ static request(args) {
22
+ var path = args.path
23
+
24
+ if (args.pathParams) {
25
+ var pathParamsString = qs.stringify(args.pathParams, {"arrayFormat": "brackets"})
26
+ path += `?${pathParamsString}`
27
+ }
28
+
29
+ return new Promise((resolve, reject) => {
30
+ var xhr = new XMLHttpRequest()
31
+ xhr.open(args.method, path, true)
32
+
33
+ if (args.headers) {
34
+ for(var headerName in args.headers) {
35
+ xhr.setRequestHeader(headerName, args.headers[headerName])
36
+ }
37
+ }
38
+
39
+ xhr.onload = () => {
40
+ var response = this._parseResponse(xhr)
41
+
42
+ if (xhr.status == 200) {
43
+ resolve(response)
44
+ } else {
45
+ reject(new CustomError(`Request failed with code: ${xhr.status}`, {response: response}))
46
+ }
47
+ }
48
+
49
+ xhr.send(args.data)
50
+ })
51
+ }
52
+
53
+ static requestLocal(args) {
54
+ if (!args.headers)
55
+ args["headers"] = {}
56
+
57
+ var token = this._token()
58
+ if (token)
59
+ args["headers"]["X-CSRF-Token"] = token
60
+
61
+ if (args.data) {
62
+ args["headers"]["Content-Type"] = "application/json"
63
+ args["data"] = JSON.stringify(args.data)
64
+ }
65
+
66
+ if (args.rawData)
67
+ args["data"] = args.rawData
68
+
69
+ return this.request(args)
70
+ }
71
+
72
+ static put(path, data = {}) {
73
+ return this.requestLocal({"path": path, "data": data, "method": "PUT"})
74
+ }
75
+
76
+ static _token() {
77
+ var tokenElement = document.querySelector("meta[name='csrf-token']")
78
+
79
+ if (tokenElement)
80
+ return tokenElement.getAttribute("content")
81
+ }
82
+
83
+ static _parseResponse(xhr) {
84
+ var responseType = xhr.getResponseHeader("content-type")
85
+
86
+ if (responseType && responseType.startsWith("application/json")) {
87
+ return JSON.parse(xhr.responseText)
88
+ } else {
89
+ return xhr.responseText
90
+ }
91
+ }
92
+ }
@@ -0,0 +1,543 @@
1
+ import CableConnectionPool from "./cable-connection-pool"
2
+ import Collection from "./collection"
3
+ import CommandsPool from "./commands-pool"
4
+ import CustomError from "./custom-error"
5
+ import FormDataToObject from "./form-data-to-object"
6
+ import ModelName from "./model-name"
7
+ import Money from "js-money"
8
+ import objectToFormData from "object-to-formdata"
9
+
10
+ const inflection = require("inflection")
11
+
12
+ export default class BaseModel {
13
+ static modelClassData() {
14
+ throw new Error("modelClassData should be overriden by child")
15
+ }
16
+
17
+ static async find(id) {
18
+ var primaryKeyName = this.modelClassData().primaryKey
19
+ var query = {}
20
+ query[`${primaryKeyName}_eq`] = id
21
+
22
+ var model = await this.ransack(query).first()
23
+
24
+ if (model) {
25
+ return model
26
+ } else {
27
+ throw new CustomError("Record not found")
28
+ }
29
+ }
30
+
31
+ static modelName() {
32
+ return new ModelName({modelClassData: this.modelClassData()})
33
+ }
34
+
35
+ static ransack(query = {}) {
36
+ return new Collection({modelClass: this}, {ransack: query})
37
+ }
38
+
39
+ constructor(args = {}) {
40
+ this.changes = {}
41
+ this.newRecord = args.isNewRecord
42
+ this.relationshipsCache = {}
43
+
44
+ if (args && args.data && args.data.a) {
45
+ this._readModelDataFromArgs(args)
46
+ } else if (args.a) {
47
+ this.modelData = args.a
48
+ } else if (args) {
49
+ this.modelData = args
50
+ } else {
51
+ this.modelData = {}
52
+ }
53
+ }
54
+
55
+ isAssociationLoaded(associationName) {
56
+ if (associationName in this.relationshipsCache)
57
+ return true
58
+
59
+ return false
60
+ }
61
+
62
+ connect(eventName, callback) {
63
+ var cableSubscription = CableConnectionPool.current().connectEvent(this.modelClassData().name, this._primaryKey(), eventName, callback)
64
+ return cableSubscription
65
+ }
66
+
67
+ static connectCreated(callback) {
68
+ var cableSubscription = CableConnectionPool.current().connectCreated(this.modelClassData().name, callback)
69
+ return cableSubscription
70
+ }
71
+
72
+ connectDestroyed(callback) {
73
+ var cableSubscription = CableConnectionPool.current().connectDestroyed(this.modelClassData().name, this._primaryKey(), callback)
74
+ return cableSubscription
75
+ }
76
+
77
+ connectUpdated(callback) {
78
+ var cableSubscription = CableConnectionPool.current().connectUpdate(this.modelClassData().name, this._primaryKey(), callback)
79
+ return cableSubscription
80
+ }
81
+
82
+ assignAttributes(newAttributes) {
83
+ for(var key in newAttributes) {
84
+ var oldValue = this._getAttribute(key)
85
+ var originalValue = this.modelData[key]
86
+ var newValue = newAttributes[key]
87
+
88
+ if (newValue != oldValue) {
89
+ if (newValue == originalValue) {
90
+ delete this.changes[key]
91
+ } else {
92
+ this.changes[key] = newValue
93
+ }
94
+ }
95
+ }
96
+ }
97
+
98
+ cacheKey() {
99
+ if (this.isPersisted()) {
100
+ var keyParts = [
101
+ this.modelClassData().paramKey,
102
+ this._primaryKey()
103
+ ]
104
+
105
+ if ("updated_at" in this.modelData) {
106
+ keyParts.push(`updatedAt-${this.updatedAt().getTime()}`)
107
+ }
108
+
109
+ return keyParts.join("-")
110
+ } else {
111
+ return this.uniqueKey()
112
+ }
113
+ }
114
+
115
+ async create() {
116
+ var paramKey = this.modelClassData().paramKey
117
+ var modelData = this.getAttributes()
118
+ var dataToUse = {}
119
+ dataToUse[paramKey] = modelData
120
+
121
+ var response = await CommandsPool.addCommand({args: dataToUse, command: `${this.modelClassData().collectionName}-create`, collectionName: this.modelClassData().collectionName, primaryKey: this._primaryKey(), type: "create"}, {})
122
+
123
+ if (response.success) {
124
+ if (response.model) {
125
+ this._setNewModelData(response.model.a)
126
+ this.changes = {}
127
+ }
128
+
129
+ return {model: this, response: response}
130
+ } else {
131
+ throw new new CustomError("Response wasn't successful", {model: this, response: response})
132
+ }
133
+ }
134
+
135
+ async createRaw(data) {
136
+ var formData = FormDataToObject.toObject(data)
137
+ var response = await CommandsPool.addCommand({args: formData, command: `${this.modelClassData().collectionName}-create`, collectionName: this.modelClassData().collectionName, primaryKey: this._primaryKey(), type: "create"}, {})
138
+
139
+ if (response.success) {
140
+ if (response.model) {
141
+ this._setNewModelData(response.model.a)
142
+ this.changes = {}
143
+ }
144
+
145
+ return {model: this, response: response}
146
+ } else {
147
+ throw new CustomError("Response wasn't successful", {model: this, response: response})
148
+ }
149
+ }
150
+
151
+ async destroy() {
152
+ var response = await CommandsPool.addCommand({command: `${this.modelClassData().collectionName}-destroy`, collectionName: this.modelClassData().collectionName, primaryKey: this._primaryKey(), type: "destroy"}, {})
153
+
154
+ if (response.success) {
155
+ if (response.model) {
156
+ this._setNewModelData(response.model.a)
157
+ this.changes = {}
158
+ }
159
+
160
+ return {model: this, response: response}
161
+ } else {
162
+ throw new CustomError("Response wasn't successful", {model: this, response: response})
163
+ }
164
+ }
165
+
166
+ getAttributes() {
167
+ return Object.assign(this.modelData, this.changes)
168
+ }
169
+
170
+ static humanAttributeName(attributeName) {
171
+ var keyName = this.modelClassData().i18nKey
172
+ return I18n.t(`activerecord.attributes.${keyName}.${BaseModel.snakeCase(attributeName)}`)
173
+ }
174
+
175
+ static snakeCase(string) {
176
+ return inflection.underscore(string)
177
+ }
178
+
179
+ isAttributeChanged(attributeName) {
180
+ var attributeNameUnderscore = inflection.underscore(attributeName)
181
+ var attributeData = this.modelClassData().attributes.find(attribute => attribute.name == attributeNameUnderscore)
182
+
183
+ if (!attributeData) {
184
+ var attributeNames = this.modelClassData().attributes.map(attribute => attribute.name)
185
+ throw new Error(`Couldn't find an attribute by that name: "${attributeName}" in: ${attributeNames.join(", ")}`)
186
+ }
187
+
188
+ if (!(attributeNameUnderscore in this.changes))
189
+ return false
190
+
191
+ var oldValue = this.modelData[attributeNameUnderscore]
192
+ var newValue = this.changes[attributeNameUnderscore]
193
+ var changedMethod = this[`_is${inflection.camelize(attributeData.type, true)}Changed`]
194
+
195
+ if (!changedMethod)
196
+ throw new Error(`Don't know how to handle type: ${attributeData.type}`)
197
+
198
+ return changedMethod(oldValue, newValue)
199
+ }
200
+
201
+ savedChangeToAttribute(attributeName) {
202
+ if (!this.previousModelData)
203
+ return false
204
+
205
+ var attributeNameUnderscore = inflection.underscore(attributeName)
206
+ var attributeData = this.modelClassData().attributes.find(attribute => attribute.name == attributeNameUnderscore)
207
+
208
+ if (!attributeData) {
209
+ var attributeNames = this.modelClassData().attributes.map(attribute => attribute.name)
210
+ throw new Error(`Couldn't find an attribute by that name: "${attributeName}" in: ${attributeNames.join(", ")}`)
211
+ }
212
+
213
+ if (!(attributeNameUnderscore in this.previousModelData))
214
+ return true
215
+
216
+ var oldValue = this.previousModelData[attributeNameUnderscore]
217
+ var newValue = this.modelData[attributeNameUnderscore]
218
+ var changedMethodName = `_is${inflection.camelize(attributeData.type)}Changed`
219
+ var changedMethod = this[changedMethodName]
220
+
221
+ if (!changedMethod)
222
+ throw new Error(`Don't know how to handle type: ${attributeData.type}`)
223
+
224
+ return changedMethod(oldValue, newValue)
225
+ }
226
+
227
+ _setNewModelData(modelData) {
228
+ this.previousModelData = this.modelData
229
+ this.modelData = modelData
230
+ }
231
+
232
+ _isDateChanged(oldValue, newValue) {
233
+ if (Date.parse(oldValue) != Date.parse(newValue))
234
+ return true
235
+ }
236
+
237
+ _isIntegerChanged(oldValue, newValue) {
238
+ if (parseInt(oldValue) != parseInt(newValue))
239
+ return true
240
+ }
241
+
242
+ _isStringChanged(oldValue, newValue) {
243
+ var oldConvertedValue = `${oldValue}`
244
+ var newConvertedValue = `${newValue}`
245
+
246
+ if (oldConvertedValue != newConvertedValue)
247
+ return true
248
+ }
249
+
250
+ isChanged() {
251
+ var keys = Object.keys(this.changes)
252
+
253
+ if (keys.length > 0) {
254
+ return true
255
+ } else {
256
+ return false
257
+ }
258
+ }
259
+
260
+ isNewRecord() {
261
+ if (this.newRecord === false) {
262
+ return false
263
+ } else if ("id" in this.modelData && this.modelData.id) {
264
+ return false
265
+ } else {
266
+ return true
267
+ }
268
+ }
269
+
270
+ isPersisted() {
271
+ return !this.isNewRecord()
272
+ }
273
+
274
+ modelClassData() {
275
+ return this.constructor.modelClassData()
276
+ }
277
+
278
+ async reload() {
279
+ var primaryKeyName = this.modelClassData().primaryKey
280
+ var query = {}
281
+ query[`${primaryKeyName}_eq`] = this._primaryKey()
282
+
283
+ var model = await this.constructor.ransack(query).first()
284
+ this._setNewModelData(model.modelData)
285
+ this.changes = {}
286
+ }
287
+
288
+ save() {
289
+ if (this.isNewRecord()) {
290
+ return this.create()
291
+ } else {
292
+ return this.update()
293
+ }
294
+ }
295
+
296
+ saveRaw(rawData) {
297
+ if (this.isNewRecord()) {
298
+ return this.createRaw(rawData)
299
+ } else {
300
+ return this.updateRaw(rawData)
301
+ }
302
+ }
303
+
304
+ async update(newAttributes = null) {
305
+ if (newAttributes)
306
+ this.assignAttributes(newAttributes)
307
+
308
+ if (this.changes.length == 0)
309
+ return resolve({model: this})
310
+
311
+ var paramKey = this.modelClassData().paramKey
312
+ var modelData = this.changes
313
+ var dataToUse = {}
314
+ dataToUse[paramKey] = modelData
315
+
316
+ var response = await CommandsPool.addCommand({args: dataToUse, command: `${this.modelClassData().collectionName}-update`, collectionName: this.modelClassData().collectionName, primaryKey: this._primaryKey(), type: "update"}, {})
317
+
318
+ if (response.success) {
319
+ if (response.model) {
320
+ this._setNewModelData(response.model.a)
321
+ this.changes = {}
322
+ }
323
+
324
+ return {"model": this, "response": response}
325
+ } else {
326
+ throw new CustomError("Response wasn't successful", {"model": this, "response": response})
327
+ }
328
+ }
329
+
330
+ async updateRaw(data) {
331
+ var formData = FormDataToObject.toObject(data)
332
+ var response = await CommandsPool.addCommand({args: formData, command: `${this.modelClassData().collectionName}-update`, collectionName: this.modelClassData().collectionName, primaryKey: this._primaryKey(), type: "update"}, {})
333
+
334
+ if (response.success) {
335
+ if (response.model) {
336
+ this._setNewModelData(response.model.a)
337
+ this.changes = {}
338
+ }
339
+
340
+ return {model: this, response: response}
341
+ } else {
342
+ throw new CustomError("Response wasn't successful", {"model": this, "response": response})
343
+ }
344
+ }
345
+
346
+ isValid() {
347
+ throw new Error("Not implemented yet")
348
+ }
349
+
350
+ async isValidOnServer() {
351
+ var modelData = this.getAttributes()
352
+ var paramKey = this.modelClassData().paramKey
353
+ var dataToUse = {}
354
+ dataToUse[paramKey] = modelData
355
+
356
+ var response = await CommandsPool.addCommand({args: dataToUse, command: `${this.modelClassData().collectionName}-valid`, collectionName: this.modelClassData().collectionName, primaryKey: this._primaryKey(), type: "valid"}, {})
357
+
358
+ return {valid: response.valid, errors: response.errors}
359
+ }
360
+
361
+ modelClass() {
362
+ return this.constructor
363
+ }
364
+
365
+ preloadRelationship(relationshipName, model) {
366
+ this.relationshipsCache[BaseModel.snakeCase(relationshipName)] = model
367
+ }
368
+
369
+ uniqueKey() {
370
+ if (!this.uniqueKeyValue) {
371
+ var min = 500000000000000000
372
+ var max = 999999999999999999
373
+ var randomBetween = Math.floor(Math.random() * (max - min + 1) + min)
374
+ this.uniqueKeyValue = randomBetween
375
+ }
376
+
377
+ return this.uniqueKeyValue
378
+ }
379
+
380
+ static _callCollectionCommand(args, commandArgs) {
381
+ return CommandsPool.addCommand(args, commandArgs)
382
+ }
383
+
384
+ _callMemberCommand(args, commandArgs) {
385
+ return CommandsPool.addCommand(args, commandArgs)
386
+ }
387
+
388
+ static _postDataFromArgs(args) {
389
+ var postData
390
+
391
+ if (args) {
392
+ if (args instanceof FormData) {
393
+ postData = args
394
+ } else {
395
+ postData = objectToFormData(args, {}, null, "args")
396
+ }
397
+ } else {
398
+ postData = new FormData()
399
+ }
400
+
401
+ return postData
402
+ }
403
+
404
+ _getAttribute(attributeName) {
405
+ if (attributeName in this.changes) {
406
+ return this.changes[attributeName]
407
+ } else if (attributeName in this.modelData) {
408
+ return this.modelData[attributeName]
409
+ } else if (this.isNewRecord()) {
410
+ // Return null if this is a new record and the attribute name is a recognized attribute
411
+ var attributes = this.modelClassData().attributes
412
+ for(var attribute of attributes) {
413
+ if (attribute.name == attributeName)
414
+ return null
415
+ }
416
+ }
417
+
418
+ throw new Error(`No such attribute: ${this.modelClassData().name}#${attributeName}`)
419
+ }
420
+
421
+ _getAttributeDateTime(attributeName) {
422
+ var value = this._getAttribute(attributeName)
423
+
424
+ if (!value) {
425
+ return value
426
+ } else if (value instanceof Date) {
427
+ return value
428
+ } else {
429
+ return new Date(value)
430
+ }
431
+ }
432
+
433
+ _isPresent(value) {
434
+ if (!value) {
435
+ return false
436
+ } else if (typeof value == "string" && value.match(/^\s*$/)) {
437
+ return false
438
+ }
439
+
440
+ return true
441
+ }
442
+
443
+ _getAttributeMoney(attributeName) {
444
+ var value = this._getAttribute(attributeName)
445
+
446
+ if (!value)
447
+ return null
448
+
449
+ var cents = value.amount
450
+ var currency = value.currency
451
+ return Money.fromInteger(cents, currency)
452
+ }
453
+
454
+ async _loadBelongsToReflection(args, queryArgs = {}) {
455
+ if (args.reflectionName in this.relationshipsCache) {
456
+ return this.relationshipsCache[args.reflectionName]
457
+ } else {
458
+ var collection = new Collection(args, queryArgs)
459
+ var model = await collection.first()
460
+ this.relationshipsCache[args.reflectionName] = model
461
+ return model
462
+ }
463
+ }
464
+
465
+ _readBelongsToReflection(args) {
466
+ if (!(args.reflectionName in this.relationshipsCache)) {
467
+ if (this.isNewRecord())
468
+ return null
469
+
470
+ throw new Error(`${this.modelClassData().name}#${args.reflectionName} hasn't been loaded yet`)
471
+ }
472
+
473
+ return this.relationshipsCache[args.reflectionName]
474
+ }
475
+
476
+ async _loadHasOneReflection(args, queryArgs = {}) {
477
+ if (args.reflectionName in this.relationshipsCache) {
478
+ return this.relationshipsCache[args.reflectionName]
479
+ } else {
480
+ var collection = new Collection(args, queryArgs)
481
+ var model = await collection.first()
482
+ this.relationshipsCache[args.reflectionName] = model
483
+ return model
484
+ }
485
+ }
486
+
487
+ _readHasOneReflection(args) {
488
+ if (!(args.reflectionName in this.relationshipsCache)) {
489
+ if (this.isNewRecord())
490
+ return null
491
+
492
+ throw new Error(`${this.modelClassData().name}#${args.reflectionName} hasn't been loaded yet`)
493
+ }
494
+
495
+ return this.relationshipsCache[args.reflectionName]
496
+ }
497
+
498
+ _readModelDataFromArgs(args) {
499
+ this.modelData = args.data.a
500
+ this.includedRelationships = args.data.r
501
+ }
502
+
503
+ _readIncludedRelationships(included) {
504
+ if (!this.includedRelationships)
505
+ return
506
+
507
+ for(var relationshipName in this.includedRelationships) {
508
+ var relationshipData = this.includedRelationships[relationshipName]
509
+ var relationshipClassData = this.modelClassData().relationships.find(relationship => relationship.name == relationshipName)
510
+
511
+ if (!relationshipClassData)
512
+ throw new Error(`No relationship on ${this.modelClassData().name} by that name: ${relationshipName}`)
513
+
514
+ var relationshipType = relationshipClassData.collectionName
515
+
516
+ if (!relationshipData) {
517
+ this.relationshipsCache[relationshipName] = null
518
+ } else if (Array.isArray(relationshipData)) {
519
+ var result = []
520
+
521
+ for(var relationshipId of relationshipData) {
522
+ var model = included.getModel(relationshipType, relationshipId)
523
+ result.push(model)
524
+ }
525
+
526
+ this.relationshipsCache[relationshipName] = result
527
+ } else {
528
+ var model = included.getModel(relationshipType, relationshipData)
529
+ this.relationshipsCache[relationshipName] = model
530
+ }
531
+ }
532
+ }
533
+
534
+ _primaryKey() {
535
+ return this._getAttribute(this.modelClassData().primaryKey)
536
+ }
537
+
538
+ static _token() {
539
+ var csrfTokenElement = document.querySelector("meta[name='csrf-token']")
540
+ if (csrfTokenElement)
541
+ return csrfTokenElement.getAttribute("content")
542
+ }
543
+ }