api_maker 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +476 -0
  4. data/Rakefile +27 -0
  5. data/app/channels/api_maker/subscriptions_channel.rb +80 -0
  6. data/app/controllers/api_maker/base_controller.rb +32 -0
  7. data/app/controllers/api_maker/commands_controller.rb +26 -0
  8. data/app/controllers/api_maker/devise_controller.rb +60 -0
  9. data/app/controllers/api_maker/session_statuses_controller.rb +33 -0
  10. data/app/services/api_maker/application_service.rb +7 -0
  11. data/app/services/api_maker/collection_command_service.rb +24 -0
  12. data/app/services/api_maker/command_response.rb +67 -0
  13. data/app/services/api_maker/command_service.rb +31 -0
  14. data/app/services/api_maker/create_command.rb +62 -0
  15. data/app/services/api_maker/create_command_service.rb +18 -0
  16. data/app/services/api_maker/destroy_command.rb +39 -0
  17. data/app/services/api_maker/destroy_command_service.rb +22 -0
  18. data/app/services/api_maker/generate_react_native_api_service.rb +61 -0
  19. data/app/services/api_maker/index_command.rb +96 -0
  20. data/app/services/api_maker/index_command_service.rb +22 -0
  21. data/app/services/api_maker/js_method_namer_service.rb +11 -0
  22. data/app/services/api_maker/member_command_service.rb +25 -0
  23. data/app/services/api_maker/model_content_generator_service.rb +108 -0
  24. data/app/services/api_maker/models_finder_service.rb +22 -0
  25. data/app/services/api_maker/models_generator_service.rb +104 -0
  26. data/app/services/api_maker/update_command.rb +43 -0
  27. data/app/services/api_maker/update_command_service.rb +21 -0
  28. data/app/services/api_maker/valid_command.rb +35 -0
  29. data/app/services/api_maker/valid_command_service.rb +21 -0
  30. data/app/views/api_maker/_data.html.erb +15 -0
  31. data/config/rails_best_practices.yml +55 -0
  32. data/config/routes.rb +7 -0
  33. data/lib/api_maker.rb +36 -0
  34. data/lib/api_maker/ability.rb +39 -0
  35. data/lib/api_maker/ability_loader.rb +21 -0
  36. data/lib/api_maker/action_controller_base_extensions.rb +5 -0
  37. data/lib/api_maker/base_command.rb +81 -0
  38. data/lib/api_maker/base_resource.rb +78 -0
  39. data/lib/api_maker/collection_serializer.rb +69 -0
  40. data/lib/api_maker/command_spec_helper.rb +57 -0
  41. data/lib/api_maker/configuration.rb +34 -0
  42. data/lib/api_maker/engine.rb +5 -0
  43. data/lib/api_maker/individual_command.rb +37 -0
  44. data/lib/api_maker/javascript/api.js +92 -0
  45. data/lib/api_maker/javascript/base-model.js +543 -0
  46. data/lib/api_maker/javascript/bootstrap/attribute-row.jsx +16 -0
  47. data/lib/api_maker/javascript/bootstrap/attribute-rows.jsx +47 -0
  48. data/lib/api_maker/javascript/bootstrap/card.jsx +79 -0
  49. data/lib/api_maker/javascript/bootstrap/checkbox.jsx +127 -0
  50. data/lib/api_maker/javascript/bootstrap/checkboxes.jsx +105 -0
  51. data/lib/api_maker/javascript/bootstrap/live-table.jsx +168 -0
  52. data/lib/api_maker/javascript/bootstrap/money-input.jsx +136 -0
  53. data/lib/api_maker/javascript/bootstrap/radio-buttons.jsx +80 -0
  54. data/lib/api_maker/javascript/bootstrap/select.jsx +168 -0
  55. data/lib/api_maker/javascript/bootstrap/string-input.jsx +203 -0
  56. data/lib/api_maker/javascript/cable-connection-pool.js +169 -0
  57. data/lib/api_maker/javascript/cable-subscription-pool.js +111 -0
  58. data/lib/api_maker/javascript/cable-subscription.js +33 -0
  59. data/lib/api_maker/javascript/collection.js +186 -0
  60. data/lib/api_maker/javascript/commands-pool.js +123 -0
  61. data/lib/api_maker/javascript/custom-error.js +14 -0
  62. data/lib/api_maker/javascript/deserializer.js +35 -0
  63. data/lib/api_maker/javascript/devise.js.erb +113 -0
  64. data/lib/api_maker/javascript/error-logger.js +119 -0
  65. data/lib/api_maker/javascript/event-connection.jsx +24 -0
  66. data/lib/api_maker/javascript/event-created.jsx +26 -0
  67. data/lib/api_maker/javascript/event-destroyed.jsx +26 -0
  68. data/lib/api_maker/javascript/event-emitter-listener.jsx +32 -0
  69. data/lib/api_maker/javascript/event-listener.jsx +41 -0
  70. data/lib/api_maker/javascript/event-updated.jsx +26 -0
  71. data/lib/api_maker/javascript/form-data-to-object.js +70 -0
  72. data/lib/api_maker/javascript/included.js +39 -0
  73. data/lib/api_maker/javascript/key-value-store.js +47 -0
  74. data/lib/api_maker/javascript/logger.js +23 -0
  75. data/lib/api_maker/javascript/model-name.js +21 -0
  76. data/lib/api_maker/javascript/model-template.js.erb +110 -0
  77. data/lib/api_maker/javascript/models-response-reader.js +43 -0
  78. data/lib/api_maker/javascript/paginate.jsx +128 -0
  79. data/lib/api_maker/javascript/params.js +68 -0
  80. data/lib/api_maker/javascript/resource-route.jsx +75 -0
  81. data/lib/api_maker/javascript/resource-routes.jsx +36 -0
  82. data/lib/api_maker/javascript/result.js +25 -0
  83. data/lib/api_maker/javascript/session-status-updater.js +113 -0
  84. data/lib/api_maker/javascript/sort-link.jsx +88 -0
  85. data/lib/api_maker/javascript/updated-attribute.jsx +60 -0
  86. data/lib/api_maker/loader.rb +14 -0
  87. data/lib/api_maker/memory_storage.rb +65 -0
  88. data/lib/api_maker/model_extensions.rb +96 -0
  89. data/lib/api_maker/permitted_params_argument.rb +12 -0
  90. data/lib/api_maker/preloader.rb +91 -0
  91. data/lib/api_maker/preloader_belongs_to.rb +58 -0
  92. data/lib/api_maker/preloader_has_many.rb +69 -0
  93. data/lib/api_maker/preloader_has_one.rb +70 -0
  94. data/lib/api_maker/preloader_through.rb +101 -0
  95. data/lib/api_maker/railtie.rb +14 -0
  96. data/lib/api_maker/relationship_includer.rb +42 -0
  97. data/lib/api_maker/resource_routing.rb +8 -0
  98. data/lib/api_maker/result_parser.rb +50 -0
  99. data/lib/api_maker/serializer.rb +86 -0
  100. data/lib/api_maker/spec_helper.rb +100 -0
  101. data/lib/api_maker/version.rb +3 -0
  102. data/lib/tasks/api_maker_tasks.rake +5 -0
  103. metadata +581 -0
@@ -0,0 +1,16 @@
1
+ import React from "react"
2
+
3
+ export default class BootstrapAttributeRow extends React.Component {
4
+ render() {
5
+ return (
6
+ <tr>
7
+ <th>
8
+ {this.props.label}
9
+ </th>
10
+ <td>
11
+ {this.props.value || this.props.children}
12
+ </td>
13
+ </tr>
14
+ )
15
+ }
16
+ }
@@ -0,0 +1,47 @@
1
+ import AttributeRow from "./attribute-row"
2
+ import PropTypes from "prop-types"
3
+ import React from "react"
4
+
5
+ export default class BootstrapAttributeRows extends React.Component {
6
+ static propTypes = {
7
+ attributes: PropTypes.array.isRequired,
8
+ model: PropTypes.object.isRequired
9
+ }
10
+
11
+ constructor(props) {
12
+ super(props)
13
+ this.state = {
14
+ classObject: props.model.modelClass()
15
+ }
16
+ }
17
+
18
+ render() {
19
+ return this.props.attributes.map((attribute) =>
20
+ <AttributeRow key={`attribute-${attribute}`} label={this.state.classObject.humanAttributeName(attribute)}>
21
+ {this.valueContent(attribute)}
22
+ </AttributeRow>
23
+ )
24
+ }
25
+
26
+ value(attribute) {
27
+ if (!(attribute in this.props.model))
28
+ throw new Error(`Attribute not found: ${this.props.model.modelClassData().name}#${attribute}`)
29
+
30
+ return this.props.model[attribute]()
31
+ }
32
+
33
+ valueContent(attribute) {
34
+ var value = this.value(attribute)
35
+
36
+ if (value instanceof Date) {
37
+ return I18n.strftime(value, "%Y-%m-%d %H:%M")
38
+ } else if (typeof value === "boolean") {
39
+ if (value)
40
+ return I18n.t("js.shared.yes")
41
+
42
+ return I18n.t("js.shared.no")
43
+ } else {
44
+ return this.value(attribute)
45
+ }
46
+ }
47
+ }
@@ -0,0 +1,79 @@
1
+ import PropTypes from "prop-types"
2
+ import PropTypesExact from "prop-types-exact"
3
+ import React from "react"
4
+
5
+ export default class Card extends React.Component {
6
+ static defaultProps = {
7
+ responsiveTable: true
8
+ }
9
+ static propTypes = PropTypesExact({
10
+ className: PropTypes.string,
11
+ children: PropTypes.node,
12
+ controls: PropTypes.node,
13
+ header: PropTypes.string,
14
+ onClick: PropTypes.func,
15
+ striped: PropTypes.bool,
16
+ style: PropTypes.object,
17
+ responsiveTable: PropTypes.bool,
18
+ table: PropTypes.bool
19
+ })
20
+
21
+ render() {
22
+ var { children, controls, header, onClick, style, table } = this.props
23
+
24
+ return (
25
+ <div className={this.classNames()} onClick={onClick} ref="card" style={style}>
26
+ {(controls || header) &&
27
+ <div className="card-header">
28
+ {header}
29
+ {controls &&
30
+ <div className="float-right">
31
+ {controls}
32
+ </div>
33
+ }
34
+ </div>
35
+ }
36
+ <div className={this.bodyClassNames()}>
37
+ {table &&
38
+ <table className={this.tableClassNames()}>
39
+ {children}
40
+ </table>
41
+ }
42
+ {!table && children}
43
+ </div>
44
+ </div>
45
+ )
46
+ }
47
+
48
+ classNames() {
49
+ var classNames = ["component-bootstrap-card", "card", "card-default"]
50
+
51
+ if (this.props.className)
52
+ classNames.push(this.props.className)
53
+
54
+ return classNames.join(" ")
55
+ }
56
+
57
+ bodyClassNames() {
58
+ var classNames = ["card-body"]
59
+
60
+ if (this.props.table) {
61
+ if (this.props.responsiveTable){
62
+ classNames.push("table-responsive")
63
+ }
64
+
65
+ classNames.push("p-0")
66
+ }
67
+
68
+ return classNames.join(" ")
69
+ }
70
+
71
+ tableClassNames() {
72
+ var classNames = ["table", "table-hover", "mb-0", "w-100"]
73
+
74
+ if (this.props.striped)
75
+ classNames.push("table-striped")
76
+
77
+ return classNames.join(" ")
78
+ }
79
+ }
@@ -0,0 +1,127 @@
1
+ import PropTypes from "prop-types"
2
+ import PropTypesExact from "prop-types-exact"
3
+ import React from "react"
4
+
5
+ const inflection = require("inflection")
6
+
7
+ export default class BootstrapCheckbox extends React.Component {
8
+ static propTypes = PropTypesExact({
9
+ attribute: PropTypes.string,
10
+ className: PropTypes.string,
11
+ "data-action": PropTypes.string,
12
+ "data-target": PropTypes.string,
13
+ defaultChecked: PropTypes.bool,
14
+ defaultValue: PropTypes.node,
15
+ hint: PropTypes.node,
16
+ id: PropTypes.string,
17
+ label: PropTypes.node,
18
+ labelClassName: PropTypes.string,
19
+ model: PropTypes.object,
20
+ name: PropTypes.string,
21
+ onChange: PropTypes.func,
22
+ wrapperClassName: PropTypes.string
23
+ })
24
+
25
+ render() {
26
+ let id = this.inputId()
27
+
28
+ return (
29
+ <div className={this.wrapperClassName()}>
30
+ <div className="form-check">
31
+ <input defaultValue="0" name={this.inputName()} type="hidden" type="hidden" />
32
+ <input
33
+ data-target={this.props["data-target"]}
34
+ defaultChecked={this.inputDefaultChecked()}
35
+ className={this.className()}
36
+ data-action={this.props["data-action"]}
37
+ defaultValue="1"
38
+ id={id}
39
+ name={this.inputName()}
40
+ onChange={this.props.onChange}
41
+ ref="input"
42
+ type="checkbox"
43
+ />
44
+
45
+ {this.label() &&
46
+ <label className={this.labelClassName()} htmlFor={id}>
47
+ {this.label()}
48
+ </label>
49
+ }
50
+ </div>
51
+ {this.props.hint &&
52
+ <p className="text-muted">
53
+ {this.props.hint}
54
+ </p>
55
+ }
56
+ </div>
57
+ )
58
+ }
59
+
60
+ className() {
61
+ var classNames = ["form-check-input"]
62
+
63
+ if (this.props.className)
64
+ classNames.push(this.props.className)
65
+
66
+ return classNames.join(" ")
67
+ }
68
+
69
+ generatedId() {
70
+ return Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15)
71
+ }
72
+
73
+ inputDefaultChecked() {
74
+ if ("defaultChecked" in this.props) {
75
+ return this.props.defaultChecked
76
+ } else if (this.props.model) {
77
+ if (!this.props.model[this.props.attribute])
78
+ throw new Error(`No such attribute: ${this.props.attribute}`)
79
+
80
+ return this.props.model[this.props.attribute]()
81
+ }
82
+ }
83
+
84
+ inputId() {
85
+ if (this.props.id) {
86
+ return this.props.id
87
+ } else if (this.props.model) {
88
+ return `${this.props.model.modelClassData().paramKey}_${inflection.underscore(this.props.attribute)}`
89
+ } else {
90
+ return this.generatedId()
91
+ }
92
+ }
93
+
94
+ inputName() {
95
+ if (this.props.name) {
96
+ return this.props.name
97
+ } else if (this.props.model) {
98
+ return `${this.props.model.modelClassData().paramKey}[${inflection.underscore(this.props.attribute)}]`
99
+ }
100
+ }
101
+
102
+ wrapperClassName() {
103
+ let classNames = ["component-bootstrap-checkbox"]
104
+
105
+ if (this.props.wrapperClassName)
106
+ classNames.push(this.props.wrapperClassName)
107
+
108
+ return classNames.join(" ")
109
+ }
110
+
111
+ label() {
112
+ if (this.props.label) {
113
+ return this.props.label
114
+ } else if (this.props.model) {
115
+ return this.props.model.modelClass().humanAttributeName(this.props.attribute)
116
+ }
117
+ }
118
+
119
+ labelClassName() {
120
+ let classNames = ["form-check-label"]
121
+
122
+ if (this.props.labelClassName)
123
+ classNames.push(this.props.labelClassName)
124
+
125
+ return classNames.join(" ")
126
+ }
127
+ }
@@ -0,0 +1,105 @@
1
+ import PropTypes from "prop-types"
2
+ import PropTypesExact from "prop-types-exact"
3
+ import React from "react"
4
+
5
+ const inflection = require("inflection")
6
+
7
+ export default class BootstrapCheckboxes extends React.Component {
8
+ static propTypes = PropTypesExact({
9
+ attribute: PropTypes.string,
10
+ defaultValue: PropTypes.array,
11
+ label: PropTypes.string,
12
+ labelClassName: PropTypes.string,
13
+ model: PropTypes.object,
14
+ name: PropTypes.string,
15
+ options: PropTypes.array.isRequired
16
+ })
17
+
18
+ render() {
19
+ return (
20
+ <div className="component-bootstrap-checkboxes form-group">
21
+ <label className={this.labelClassName()}>
22
+ {this.label()}
23
+ </label>
24
+
25
+ <input name={this.inputName()} type="hidden" value="" />
26
+ {this.props.options.map((option, index) => this.optionElement(option, index))}
27
+ </div>
28
+ )
29
+ }
30
+
31
+ inputDefaultValue() {
32
+ if (this.props.defaultValue) {
33
+ return this.props.defaultValue
34
+ } else if (this.props.model) {
35
+ if (!this.props.model[this.props.attribute])
36
+ throw `No such attribute: ${this.props.attribute}`
37
+
38
+ return this.props.model[this.props.attribute]()
39
+ }
40
+ }
41
+
42
+ inputName() {
43
+ if (this.props.name) {
44
+ return `${this.props.name}[]`
45
+ } else if (this.props.model) {
46
+ return `${this.props.model.modelClassData().paramKey}[${inflection.underscore(this.props.attribute)}]`
47
+ }
48
+ }
49
+
50
+ isDefaultSelected(option) {
51
+ let defaultValue = this.inputDefaultValue()
52
+
53
+ if (!defaultValue)
54
+ return false
55
+
56
+ if (defaultValue.constructor === Array) {
57
+ return defaultValue.includes(option)
58
+ } else {
59
+ return defaultValue == option
60
+ }
61
+ }
62
+
63
+ label() {
64
+ if (this.props.label === false) {
65
+ return null
66
+ } else if (this.props.label) {
67
+ return this.props.label
68
+ } else if (this.props.model) {
69
+ return this.props.model.modelClass().humanAttributeName(this.props.attribute)
70
+ }
71
+ }
72
+
73
+ labelClassName() {
74
+ let classNames = []
75
+
76
+ if (this.props.labelClassName)
77
+ classNames.push(this.props.labelClassName)
78
+
79
+ return classNames.join(" ")
80
+ }
81
+
82
+ generatedId() {
83
+ return Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15)
84
+ }
85
+
86
+ optionElement(option) {
87
+ var id = this.generatedId()
88
+
89
+ return (
90
+ <div className="checkboxes-option" key={`option-${option[1]}`}>
91
+ <input
92
+ data-option-value={option[1]}
93
+ defaultChecked={this.isDefaultSelected(option[1])}
94
+ id={id}
95
+ name={this.inputName()}
96
+ type="checkbox"
97
+ value={option[1]} />
98
+
99
+ <label className="ml-1" htmlFor={id}>
100
+ {option[0]}
101
+ </label>
102
+ </div>
103
+ )
104
+ }
105
+ }
@@ -0,0 +1,168 @@
1
+ import Collection from "api-maker/collection"
2
+ import EventCreated from "api-maker/event-created"
3
+ import EventDestroyed from "api-maker/event-destroyed"
4
+ import Paginate from "api-maker/paginate"
5
+ import Params from "api-maker/params"
6
+
7
+ const inflection = require("inflection")
8
+
9
+ export default class LiveTable extends React.Component {
10
+ static defaultProps = {
11
+ preloads: [],
12
+ select: {}
13
+ }
14
+
15
+ static propTypes = PropTypesExact({
16
+ className: PropTypes.string,
17
+ collection: PropTypes.instanceOf(Collection),
18
+ columnsContent: PropTypes.func.isRequired,
19
+ defaultParams: PropTypes.object,
20
+ destroyMessage: PropTypes.string,
21
+ filterContent: PropTypes.func,
22
+ filterSubmitLabel: PropTypes.node,
23
+ headersContent: PropTypes.func.isRequired,
24
+ modelClass: PropTypes.func.isRequired,
25
+ preloads: PropTypes.array.isRequired,
26
+ queryName: PropTypes.string.isRequired,
27
+ select: PropTypes.object
28
+ })
29
+
30
+ constructor(props) {
31
+ super(props)
32
+ this.state = {
33
+ currentHref: location.href,
34
+ queryQName: `${this.props.queryName}_q`,
35
+ queryPageName: `${this.props.queryName}_page`
36
+ }
37
+ }
38
+
39
+ componentDidMount() {
40
+ this.loadQParams().then(() => this.loadModels())
41
+ }
42
+
43
+ componentDidUpdate() {
44
+ if (this.state.currentHref != location.href) {
45
+ var { queryQName } = this.state
46
+ var params = Params.parse()
47
+ var qParams = params[queryQName] || {}
48
+ Params.setCachedParams(queryQName, qParams)
49
+ this.setState({currentHref: location.href, qParams}, () => this.loadModels())
50
+ }
51
+ }
52
+
53
+ async loadQParams() {
54
+ var { queryQName } = this.state
55
+ var qParams = await Params.getCachedParams(queryQName, {default: this.props.defaultParams || {}})
56
+ return this.setState({qParams})
57
+ }
58
+
59
+ async loadModels() {
60
+ var params = Params.parse()
61
+ var { modelClass, preloads, select } = this.props
62
+ var { qParams, queryPageName, queryQName } = this.state
63
+ var query
64
+
65
+ if (this.props.collection) {
66
+ query = this.props.collection
67
+ } else {
68
+ query = modelClass
69
+ }
70
+
71
+ query = query
72
+ .ransack(qParams)
73
+ .searchKey(queryQName)
74
+ .page(params[queryPageName])
75
+ .pageKey(queryPageName)
76
+ .preload(preloads)
77
+ .select(select)
78
+
79
+ var result = await query.result()
80
+
81
+ this.setState({query, result, models: result.models()})
82
+ }
83
+
84
+ render() {
85
+ var { qParams, query, result, models } = this.state
86
+
87
+ return (
88
+ <div className={this.className()}>
89
+ {qParams && query && result && models && this.content()}
90
+ </div>
91
+ )
92
+ }
93
+
94
+ content() {
95
+ var { filterContent, filterSubmitLabel, modelClass } = this.props
96
+ var { qParams, query, result, models } = this.state
97
+
98
+ return (
99
+ <div className="content-container">
100
+ <EventCreated modelClass={modelClass} onCreated={() => this.onModelCreated()} />
101
+
102
+ {filterContent &&
103
+ <Card className="mb-4">
104
+ <form onSubmit={(e) => this.onFilterFormSubmit(e)} ref="filterForm">
105
+ {filterContent({qParams})}
106
+ <input className="btn btn-primary" label={filterSubmitLabel} type="submit" />
107
+ </form>
108
+ </Card>
109
+ }
110
+
111
+ {models.map(model =>
112
+ <EventDestroyed key={`event-destroyed-${model.cacheKey()}`} model={model} onDestroyed={(args) => this.onModelDestroyed(args)} />
113
+ )}
114
+
115
+ <Card className="mb-4" striped table>
116
+ <thead>
117
+ <tr>
118
+ {this.props.headersContent({query})}
119
+ </tr>
120
+ </thead>
121
+ <tbody>
122
+ {models.map(model =>
123
+ <tr className={`${inflection.singularize(modelClass.modelClassData().collectionName)}-row`} data-model-id={model.id()} key={model.cacheKey()}>
124
+ {this.props.columnsContent({model})}
125
+ </tr>
126
+ )}
127
+ </tbody>
128
+ </Card>
129
+
130
+ <Paginate result={result} />
131
+ </div>
132
+ )
133
+ }
134
+
135
+ className() {
136
+ var classNames = ["component-api-maker-live-table"]
137
+
138
+ if (this.props.className)
139
+ classNames.push(this.props.className)
140
+
141
+ return classNames.join(" ")
142
+ }
143
+
144
+ onFilterFormSubmit(e) {
145
+ e.preventDefault()
146
+
147
+ var qParams = Params.serializeForm(this.refs.filterForm)
148
+ var { queryQName } = this.state
149
+
150
+ var changeParamsParams = {}
151
+ changeParamsParams[queryQName] = qParams
152
+
153
+ Params.changeParams(changeParamsParams)
154
+ Params.setCachedParams(queryQName, qParams)
155
+
156
+ this.setState({currentHref: location.href, qParams}, () => this.loadModels())
157
+ }
158
+
159
+ onModelCreated() {
160
+ this.loadModels()
161
+ }
162
+
163
+ onModelDestroyed(args) {
164
+ this.setState({
165
+ models: this.state.models.filter(model => model.id() != args.model.id())
166
+ })
167
+ }
168
+ }