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,88 @@
|
|
|
1
|
+
import { Link } from "react-router-dom"
|
|
2
|
+
import qs from "qs"
|
|
3
|
+
import React from "react"
|
|
4
|
+
|
|
5
|
+
const inflection = require("inflection")
|
|
6
|
+
|
|
7
|
+
export default class extends React.Component {
|
|
8
|
+
constructor(props) {
|
|
9
|
+
super(props)
|
|
10
|
+
|
|
11
|
+
var searchKey = this.props.query.queryArgs.searchKey
|
|
12
|
+
if (!searchKey)
|
|
13
|
+
searchKey = "q"
|
|
14
|
+
|
|
15
|
+
this.state = {searchKey: searchKey}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
attribute() {
|
|
19
|
+
return inflection.underscore(this.props.attribute)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
href() {
|
|
23
|
+
var currentParams = qs.parse(window.location.search.substr(1))
|
|
24
|
+
|
|
25
|
+
if (!currentParams[this.state.searchKey])
|
|
26
|
+
currentParams[this.state.searchKey] = {}
|
|
27
|
+
|
|
28
|
+
currentParams[this.state.searchKey]["s"] = `${this.attribute()} ${this.sortMode()}`
|
|
29
|
+
|
|
30
|
+
var newParams = qs.stringify(currentParams)
|
|
31
|
+
var newPath = `${location.pathname}?${newParams}`
|
|
32
|
+
|
|
33
|
+
return newPath
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
isSortedByAttribute() {
|
|
37
|
+
if (this.props.query.queryArgs.ransack && this.props.query.queryArgs.ransack.s == this.attribute())
|
|
38
|
+
return true
|
|
39
|
+
|
|
40
|
+
if (this.props.query.queryArgs.ransack && this.props.query.queryArgs.ransack.s == `${this.attribute()} asc`)
|
|
41
|
+
return true
|
|
42
|
+
|
|
43
|
+
return false
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
render() {
|
|
47
|
+
var LinkComponent = this.linkComponent()
|
|
48
|
+
var { attribute, className, linkComponent, query, title, ...other } = this.props
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<LinkComponent {...other} className={this.className()} data-attribute={attribute} data-sort-mode={this.sortMode()} to={this.href()}>
|
|
52
|
+
{this.title()}
|
|
53
|
+
</LinkComponent>
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
className() {
|
|
58
|
+
var classNames = ["component-api-maker-bootstrap-sort-link"]
|
|
59
|
+
|
|
60
|
+
if (this.props.className)
|
|
61
|
+
classNames.push(this.props.className)
|
|
62
|
+
|
|
63
|
+
return classNames.join(" ")
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
linkComponent() {
|
|
67
|
+
if (this.props.linkComponent) {
|
|
68
|
+
return this.props.linkComponent
|
|
69
|
+
} else {
|
|
70
|
+
return Link
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
sortMode() {
|
|
75
|
+
if (this.isSortedByAttribute()) {
|
|
76
|
+
return "desc"
|
|
77
|
+
} else {
|
|
78
|
+
return "asc"
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
title() {
|
|
83
|
+
if (this.props.title)
|
|
84
|
+
return this.props.title
|
|
85
|
+
|
|
86
|
+
return this.props.query.modelClass().humanAttributeName(this.props.attribute)
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import PropTypes from "prop-types"
|
|
2
|
+
import PropTypesExact from "prop-types-exact"
|
|
3
|
+
import React from "react"
|
|
4
|
+
|
|
5
|
+
export default class ApiMakerUpdatedAttribute extends React.Component {
|
|
6
|
+
static propTypes = PropTypesExact({
|
|
7
|
+
attribute: PropTypes.string,
|
|
8
|
+
model: PropTypes.object.isRequired,
|
|
9
|
+
onValue: PropTypes.func
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
constructor(props) {
|
|
13
|
+
super(props)
|
|
14
|
+
this.state = {
|
|
15
|
+
model: this.props.model
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
componentDidMount() {
|
|
20
|
+
this.setAttribute()
|
|
21
|
+
this.connect()
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
componentWillUnmount() {
|
|
25
|
+
this.connectUpdated.unsubscribe()
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
connect() {
|
|
29
|
+
this.connectUpdated = this.props.model.connectUpdated(args =>
|
|
30
|
+
this.setState(
|
|
31
|
+
{model: args.model},
|
|
32
|
+
() => this.setAttribute()
|
|
33
|
+
)
|
|
34
|
+
)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
setAttribute() {
|
|
38
|
+
var newValue
|
|
39
|
+
|
|
40
|
+
if (this.props.onValue) {
|
|
41
|
+
newValue = this.props.onValue.apply(null, [{model: this.state.model}])
|
|
42
|
+
} else {
|
|
43
|
+
if (!this.state.model[this.props.attribute])
|
|
44
|
+
throw new Error(`No such method: ${this.state.model.modelClassData().name}#${this.props.attribute}()`)
|
|
45
|
+
|
|
46
|
+
newValue = this.state.model[this.props.attribute].apply(this.state.model)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
this.setState({
|
|
50
|
+
value: newValue
|
|
51
|
+
})
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
render() {
|
|
55
|
+
if (this.state.value === undefined)
|
|
56
|
+
return ""
|
|
57
|
+
|
|
58
|
+
return this.state.value
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
class ApiMaker::Loader
|
|
2
|
+
def self.load_everything
|
|
3
|
+
return if @loaded
|
|
4
|
+
|
|
5
|
+
@loaded = true
|
|
6
|
+
|
|
7
|
+
resources_dir = Rails.root.join("app", "api_maker", "resources")
|
|
8
|
+
files = Dir.glob("#{resources_dir}/**/*.rb")
|
|
9
|
+
|
|
10
|
+
files.each do |file|
|
|
11
|
+
require file
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
class ApiMaker::MemoryStorage
|
|
2
|
+
attr_reader :model_class_for_data, :storage
|
|
3
|
+
|
|
4
|
+
def self.current
|
|
5
|
+
@current ||= ApiMaker::MemoryStorage.new
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def initialize
|
|
9
|
+
@model_class_for_data = {}
|
|
10
|
+
@storage = {}
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def storage_for(klass, mode)
|
|
14
|
+
@storage.dig(klass, mode) || {}
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def add(klass, mode, data, args = {})
|
|
18
|
+
@storage[klass] ||= {}
|
|
19
|
+
@storage[klass][mode] ||= {}
|
|
20
|
+
@storage[klass][mode][data] = {data: data, args: args} unless @storage[klass][mode].key?(data)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def load_all_resources
|
|
24
|
+
resources_path = Rails.root.join("app", "api_maker", "resources")
|
|
25
|
+
|
|
26
|
+
Dir.foreach(resources_path) do |file|
|
|
27
|
+
match = file.match(/\A((.+)_resource)\.rb\Z/)
|
|
28
|
+
next unless match
|
|
29
|
+
|
|
30
|
+
resource_name = match[1]
|
|
31
|
+
resource_class_name = "Resources::#{resource_name.camelize}"
|
|
32
|
+
|
|
33
|
+
# Load the resource by constantizing it to support auto loading
|
|
34
|
+
resource_class_name.safe_constantize
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
@resources_loaded = true
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def resources_loaded?
|
|
41
|
+
@resources_loaded
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def model_class_for(resource:, klass:)
|
|
45
|
+
model_class_for_data[klass.name] = resource.name
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def resource_for_model(model_class)
|
|
49
|
+
class_name = resource_name_for_model(model_class)
|
|
50
|
+
resource_class = class_name.safe_constantize
|
|
51
|
+
|
|
52
|
+
if !resource_class && !resources_loaded?
|
|
53
|
+
load_all_resources # Some resources with custom model class won't have been loaded at this point
|
|
54
|
+
return resource_for_model(model_class)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
raise "Resource couldn't be found from model: #{model_class}" unless resource_class
|
|
58
|
+
|
|
59
|
+
resource_class
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def resource_name_for_model(model_class)
|
|
63
|
+
model_class_for_data[model_class.name] || "Resources::#{model_class.name.gsub("::", "")}Resource"
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
module ApiMaker::ModelExtensions
|
|
2
|
+
def self.included(base)
|
|
3
|
+
base.extend(ClassMethods)
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
module ClassMethods
|
|
7
|
+
def api_maker_broadcast_creates
|
|
8
|
+
after_commit on: :create do |model| # rubocop:disable Style/SymbolProc
|
|
9
|
+
model.api_maker_broadcast_create
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def api_maker_broadcast_create_channel_name
|
|
14
|
+
@api_maker_broadcast_create_channel_name ||= "api_maker_creates_#{api_maker_resource.short_name}"
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def api_maker_broadcast_updates
|
|
18
|
+
after_commit on: :update do |model| # rubocop:disable Style/SymbolProc
|
|
19
|
+
model.api_maker_broadcast_update
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def api_maker_broadcast_destroys
|
|
24
|
+
after_commit on: :destroy do |model| # rubocop:disable Style/SymbolProc
|
|
25
|
+
model.api_maker_broadcast_destroy
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def api_maker_resource
|
|
30
|
+
@api_maker_resource ||= ApiMaker::MemoryStorage.current.resource_for_model(self)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def api_maker_event(event_name, args = {})
|
|
35
|
+
channel_name = api_maker_event_channel_name(event_name)
|
|
36
|
+
serializer = ApiMaker::Serializer.new(model: self)
|
|
37
|
+
data_to_broadcast = ApiMaker::ResultParser.parse(
|
|
38
|
+
args: args,
|
|
39
|
+
event_name: event_name,
|
|
40
|
+
model_id: id,
|
|
41
|
+
model_type: serializer.resource.collection_name,
|
|
42
|
+
type: :event
|
|
43
|
+
)
|
|
44
|
+
ActionCable.server.broadcast(channel_name, data_to_broadcast)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def api_maker_event_channel_name(event_name)
|
|
48
|
+
"api_maker_events_#{api_maker_resource.short_name}_#{id}_#{event_name}"
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def api_maker_broadcast_create
|
|
52
|
+
serializer = ApiMaker::Serializer.new(model: self)
|
|
53
|
+
data_to_broadcast = ApiMaker::ResultParser.parse(
|
|
54
|
+
model: self,
|
|
55
|
+
model_class_name: self.class.name,
|
|
56
|
+
model_id: id,
|
|
57
|
+
model_type: serializer.resource.collection_name,
|
|
58
|
+
type: :create
|
|
59
|
+
)
|
|
60
|
+
ActionCable.server.broadcast(self.class.api_maker_broadcast_create_channel_name, data_to_broadcast)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def api_maker_broadcast_destroy
|
|
64
|
+
serializer = ApiMaker::Serializer.new(model: self)
|
|
65
|
+
data_to_broadcast = ApiMaker::ResultParser.parse(
|
|
66
|
+
model: self,
|
|
67
|
+
model_id: id,
|
|
68
|
+
model_type: serializer.resource.collection_name,
|
|
69
|
+
type: :destroy
|
|
70
|
+
)
|
|
71
|
+
ActionCable.server.broadcast(api_maker_broadcast_destroy_channel_name, data_to_broadcast)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def api_maker_broadcast_destroy_channel_name
|
|
75
|
+
@api_maker_broadcast_destroy_channel_name ||= "api_maker_destroys_#{api_maker_resource.short_name}_#{id}"
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def api_maker_broadcast_update
|
|
79
|
+
serializer = ApiMaker::Serializer.new(model: self)
|
|
80
|
+
data_to_broadcast = ApiMaker::ResultParser.parse(
|
|
81
|
+
model: self,
|
|
82
|
+
model_id: id,
|
|
83
|
+
model_type: serializer.resource.collection_name,
|
|
84
|
+
type: :update
|
|
85
|
+
)
|
|
86
|
+
ActionCable.server.broadcast(api_maker_broadcast_update_channel_name, data_to_broadcast)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def api_maker_broadcast_update_channel_name
|
|
90
|
+
@api_maker_broadcast_update_channel_name ||= "api_maker_updates_#{api_maker_resource.short_name}_#{id}"
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def api_maker_resource
|
|
94
|
+
@api_maker_resource ||= self.class.api_maker_resource
|
|
95
|
+
end
|
|
96
|
+
end
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
class ApiMaker::Preloader
|
|
2
|
+
def initialize(ability: nil, args: nil, collection:, data:, include_param:, records:, select:)
|
|
3
|
+
@ability = ability
|
|
4
|
+
@args = args
|
|
5
|
+
@collection = collection
|
|
6
|
+
@data = data
|
|
7
|
+
@include_param = include_param
|
|
8
|
+
@records = records
|
|
9
|
+
@select = select
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def fill_data
|
|
13
|
+
parsed = ApiMaker::RelationshipIncluder.parse(@include_param)
|
|
14
|
+
return unless parsed
|
|
15
|
+
|
|
16
|
+
parsed.each do |key, value|
|
|
17
|
+
next unless key
|
|
18
|
+
|
|
19
|
+
reflection = @collection.model.reflections[key]
|
|
20
|
+
raise "Unknown reflection: #{@collection.model.name}##{key}" unless reflection
|
|
21
|
+
|
|
22
|
+
fill_empty_relationships_for_key(reflection, key)
|
|
23
|
+
preload_class = preload_class_for_key(reflection)
|
|
24
|
+
|
|
25
|
+
preload_result = ApiMaker::Configuration.profile("Preloading #{reflection.klass.name} with #{preload_class.name}") do
|
|
26
|
+
preload_class.new(
|
|
27
|
+
ability: @ability,
|
|
28
|
+
args: @args,
|
|
29
|
+
collection: @collection,
|
|
30
|
+
data: @data,
|
|
31
|
+
records: @records,
|
|
32
|
+
reflection: reflection,
|
|
33
|
+
select: @select
|
|
34
|
+
).preload
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
next if value.blank? || preload_result.fetch(:collection).empty?
|
|
38
|
+
|
|
39
|
+
ApiMaker::Preloader.new(
|
|
40
|
+
ability: @ability,
|
|
41
|
+
args: @args,
|
|
42
|
+
data: @data,
|
|
43
|
+
collection: preload_result.fetch(:collection),
|
|
44
|
+
include_param: value,
|
|
45
|
+
records: @data.fetch(:included),
|
|
46
|
+
select: @select
|
|
47
|
+
).fill_data
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
private
|
|
52
|
+
|
|
53
|
+
def fill_empty_relationships_for_key(reflection, key)
|
|
54
|
+
if @records.is_a?(Hash)
|
|
55
|
+
collection_name = ApiMaker::MemoryStorage.current.resource_for_model(reflection.active_record).collection_name
|
|
56
|
+
records_to_set = @records.fetch(collection_name).values
|
|
57
|
+
else
|
|
58
|
+
records_to_set = @records.select { |record| record.model.class == reflection.active_record }
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
case reflection.macro
|
|
62
|
+
when :has_many
|
|
63
|
+
records_to_set.each do |model|
|
|
64
|
+
model.relationships[key.to_sym] ||= []
|
|
65
|
+
end
|
|
66
|
+
when :belongs_to
|
|
67
|
+
records_to_set.each do |model|
|
|
68
|
+
model.relationships[key.to_sym] ||= nil
|
|
69
|
+
end
|
|
70
|
+
when :has_one
|
|
71
|
+
records_to_set.each do |model|
|
|
72
|
+
model.relationships[key.to_sym] ||= nil
|
|
73
|
+
end
|
|
74
|
+
else
|
|
75
|
+
raise "Unknown macro: #{reflection.macro}"
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def preload_class_for_key(reflection)
|
|
80
|
+
case reflection.macro
|
|
81
|
+
when :has_many
|
|
82
|
+
ApiMaker::PreloaderHasMany
|
|
83
|
+
when :belongs_to
|
|
84
|
+
ApiMaker::PreloaderBelongsTo
|
|
85
|
+
when :has_one
|
|
86
|
+
ApiMaker::PreloaderHasOne
|
|
87
|
+
else
|
|
88
|
+
raise "Unknown macro: #{reflection.macro}"
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
class ApiMaker::PreloaderBelongsTo
|
|
2
|
+
def initialize(ability:, args:, data:, collection:, records:, reflection:, select:)
|
|
3
|
+
@ability = ability
|
|
4
|
+
@args = args
|
|
5
|
+
@data = data
|
|
6
|
+
@collection = collection
|
|
7
|
+
@reflection = reflection
|
|
8
|
+
@reflection_name = @reflection.name
|
|
9
|
+
@records = records
|
|
10
|
+
@select = select
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def preload
|
|
14
|
+
models.each do |model|
|
|
15
|
+
records_for_model(model).each do |record|
|
|
16
|
+
record.relationships[@reflection_name] = model.id
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
serializer = ApiMaker::Serializer.new(ability: @ability, args: @args, model: model, select: @select&.dig(model.class))
|
|
20
|
+
collection_name = serializer.resource.collection_name
|
|
21
|
+
|
|
22
|
+
@data.fetch(:included)[collection_name] ||= {}
|
|
23
|
+
@data.fetch(:included).fetch(collection_name)[model.id] ||= serializer
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
{collection: models}
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def collection_name
|
|
32
|
+
@collection_name = ApiMaker::MemoryStorage.current.resource_for_model(@reflection.active_record).collection_name
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def model_class
|
|
36
|
+
@model_class ||= @reflection.klass
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def models
|
|
40
|
+
@models ||= begin
|
|
41
|
+
models = @reflection.klass.where(look_up_key => @collection.map(&@reflection.foreign_key.to_sym).uniq)
|
|
42
|
+
models = models.accessible_by(@ability) if @ability
|
|
43
|
+
models.load
|
|
44
|
+
models
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def look_up_key
|
|
49
|
+
@look_up_key ||= @reflection.options[:primary_key] || @reflection.klass.primary_key
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def records_for_model(model)
|
|
53
|
+
@records
|
|
54
|
+
.fetch(collection_name)
|
|
55
|
+
.values
|
|
56
|
+
.select { |record| record.model.read_attribute(@reflection.foreign_key) == model.read_attribute(look_up_key) }
|
|
57
|
+
end
|
|
58
|
+
end
|