api_maker 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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
+ }