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.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +476 -0
- data/Rakefile +27 -0
- data/app/channels/api_maker/subscriptions_channel.rb +80 -0
- data/app/controllers/api_maker/base_controller.rb +32 -0
- data/app/controllers/api_maker/commands_controller.rb +26 -0
- data/app/controllers/api_maker/devise_controller.rb +60 -0
- data/app/controllers/api_maker/session_statuses_controller.rb +33 -0
- data/app/services/api_maker/application_service.rb +7 -0
- data/app/services/api_maker/collection_command_service.rb +24 -0
- data/app/services/api_maker/command_response.rb +67 -0
- data/app/services/api_maker/command_service.rb +31 -0
- data/app/services/api_maker/create_command.rb +62 -0
- data/app/services/api_maker/create_command_service.rb +18 -0
- data/app/services/api_maker/destroy_command.rb +39 -0
- data/app/services/api_maker/destroy_command_service.rb +22 -0
- data/app/services/api_maker/generate_react_native_api_service.rb +61 -0
- data/app/services/api_maker/index_command.rb +96 -0
- data/app/services/api_maker/index_command_service.rb +22 -0
- data/app/services/api_maker/js_method_namer_service.rb +11 -0
- data/app/services/api_maker/member_command_service.rb +25 -0
- data/app/services/api_maker/model_content_generator_service.rb +108 -0
- data/app/services/api_maker/models_finder_service.rb +22 -0
- data/app/services/api_maker/models_generator_service.rb +104 -0
- data/app/services/api_maker/update_command.rb +43 -0
- data/app/services/api_maker/update_command_service.rb +21 -0
- data/app/services/api_maker/valid_command.rb +35 -0
- data/app/services/api_maker/valid_command_service.rb +21 -0
- data/app/views/api_maker/_data.html.erb +15 -0
- data/config/rails_best_practices.yml +55 -0
- data/config/routes.rb +7 -0
- data/lib/api_maker.rb +36 -0
- data/lib/api_maker/ability.rb +39 -0
- data/lib/api_maker/ability_loader.rb +21 -0
- data/lib/api_maker/action_controller_base_extensions.rb +5 -0
- data/lib/api_maker/base_command.rb +81 -0
- data/lib/api_maker/base_resource.rb +78 -0
- data/lib/api_maker/collection_serializer.rb +69 -0
- data/lib/api_maker/command_spec_helper.rb +57 -0
- data/lib/api_maker/configuration.rb +34 -0
- data/lib/api_maker/engine.rb +5 -0
- data/lib/api_maker/individual_command.rb +37 -0
- data/lib/api_maker/javascript/api.js +92 -0
- data/lib/api_maker/javascript/base-model.js +543 -0
- data/lib/api_maker/javascript/bootstrap/attribute-row.jsx +16 -0
- data/lib/api_maker/javascript/bootstrap/attribute-rows.jsx +47 -0
- data/lib/api_maker/javascript/bootstrap/card.jsx +79 -0
- data/lib/api_maker/javascript/bootstrap/checkbox.jsx +127 -0
- data/lib/api_maker/javascript/bootstrap/checkboxes.jsx +105 -0
- data/lib/api_maker/javascript/bootstrap/live-table.jsx +168 -0
- data/lib/api_maker/javascript/bootstrap/money-input.jsx +136 -0
- data/lib/api_maker/javascript/bootstrap/radio-buttons.jsx +80 -0
- data/lib/api_maker/javascript/bootstrap/select.jsx +168 -0
- data/lib/api_maker/javascript/bootstrap/string-input.jsx +203 -0
- data/lib/api_maker/javascript/cable-connection-pool.js +169 -0
- data/lib/api_maker/javascript/cable-subscription-pool.js +111 -0
- data/lib/api_maker/javascript/cable-subscription.js +33 -0
- data/lib/api_maker/javascript/collection.js +186 -0
- data/lib/api_maker/javascript/commands-pool.js +123 -0
- data/lib/api_maker/javascript/custom-error.js +14 -0
- data/lib/api_maker/javascript/deserializer.js +35 -0
- data/lib/api_maker/javascript/devise.js.erb +113 -0
- data/lib/api_maker/javascript/error-logger.js +119 -0
- data/lib/api_maker/javascript/event-connection.jsx +24 -0
- data/lib/api_maker/javascript/event-created.jsx +26 -0
- data/lib/api_maker/javascript/event-destroyed.jsx +26 -0
- data/lib/api_maker/javascript/event-emitter-listener.jsx +32 -0
- data/lib/api_maker/javascript/event-listener.jsx +41 -0
- data/lib/api_maker/javascript/event-updated.jsx +26 -0
- data/lib/api_maker/javascript/form-data-to-object.js +70 -0
- data/lib/api_maker/javascript/included.js +39 -0
- data/lib/api_maker/javascript/key-value-store.js +47 -0
- data/lib/api_maker/javascript/logger.js +23 -0
- data/lib/api_maker/javascript/model-name.js +21 -0
- data/lib/api_maker/javascript/model-template.js.erb +110 -0
- data/lib/api_maker/javascript/models-response-reader.js +43 -0
- data/lib/api_maker/javascript/paginate.jsx +128 -0
- data/lib/api_maker/javascript/params.js +68 -0
- data/lib/api_maker/javascript/resource-route.jsx +75 -0
- data/lib/api_maker/javascript/resource-routes.jsx +36 -0
- data/lib/api_maker/javascript/result.js +25 -0
- data/lib/api_maker/javascript/session-status-updater.js +113 -0
- data/lib/api_maker/javascript/sort-link.jsx +88 -0
- data/lib/api_maker/javascript/updated-attribute.jsx +60 -0
- data/lib/api_maker/loader.rb +14 -0
- data/lib/api_maker/memory_storage.rb +65 -0
- data/lib/api_maker/model_extensions.rb +96 -0
- data/lib/api_maker/permitted_params_argument.rb +12 -0
- data/lib/api_maker/preloader.rb +91 -0
- data/lib/api_maker/preloader_belongs_to.rb +58 -0
- data/lib/api_maker/preloader_has_many.rb +69 -0
- data/lib/api_maker/preloader_has_one.rb +70 -0
- data/lib/api_maker/preloader_through.rb +101 -0
- data/lib/api_maker/railtie.rb +14 -0
- data/lib/api_maker/relationship_includer.rb +42 -0
- data/lib/api_maker/resource_routing.rb +8 -0
- data/lib/api_maker/result_parser.rb +50 -0
- data/lib/api_maker/serializer.rb +86 -0
- data/lib/api_maker/spec_helper.rb +100 -0
- data/lib/api_maker/version.rb +3 -0
- data/lib/tasks/api_maker_tasks.rake +5 -0
- 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,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
|
+
}
|