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,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,12 @@
1
+ class ApiMaker::PermittedParamsArgument
2
+ attr_reader :command, :model
3
+
4
+ def initialize(command:, model:)
5
+ @command = command
6
+ @model = model
7
+ end
8
+
9
+ def params
10
+ command.args
11
+ end
12
+ 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