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