api_maker 0.0.1 → 0.0.2

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 (135) hide show
  1. checksums.yaml +4 -4
  2. data/app/api_maker/api_helpers/api_maker_helpers.rb +5 -0
  3. data/app/api_maker/services/can_can/load_abilities.rb +30 -0
  4. data/app/api_maker/services/devise/sign_in.rb +64 -0
  5. data/app/api_maker/services/devise/sign_out.rb +9 -0
  6. data/app/api_maker/services/models/find_or_create_by.rb +18 -0
  7. data/app/channels/api_maker/subscriptions_channel.rb +33 -2
  8. data/app/controllers/api_maker/base_controller.rb +7 -3
  9. data/app/controllers/api_maker/commands_controller.rb +26 -4
  10. data/app/controllers/api_maker/session_statuses_controller.rb +1 -1
  11. data/app/services/api_maker/abilities_loader.rb +104 -0
  12. data/app/services/api_maker/application_service.rb +2 -1
  13. data/app/services/api_maker/base_command.rb +248 -0
  14. data/app/services/api_maker/collection_command_service.rb +29 -15
  15. data/app/services/api_maker/collection_loader.rb +124 -0
  16. data/app/services/api_maker/command_failed_error.rb +3 -0
  17. data/app/services/api_maker/command_response.rb +17 -6
  18. data/app/services/api_maker/command_service.rb +3 -3
  19. data/app/services/api_maker/create_command.rb +11 -26
  20. data/app/services/api_maker/create_command_service.rb +3 -3
  21. data/app/services/api_maker/database_type.rb +9 -0
  22. data/app/services/api_maker/deep_merge_params.rb +26 -0
  23. data/app/services/api_maker/deserializer.rb +35 -0
  24. data/app/services/api_maker/destroy_command.rb +15 -21
  25. data/app/services/api_maker/destroy_command_service.rb +3 -3
  26. data/app/services/api_maker/generate_react_native_api_service.rb +3 -19
  27. data/app/services/api_maker/include_helpers.rb +17 -0
  28. data/app/services/api_maker/index_command.rb +8 -88
  29. data/app/services/api_maker/index_command_service.rb +5 -5
  30. data/app/services/api_maker/js_method_namer_service.rb +1 -1
  31. data/app/services/api_maker/locals_from_controller.rb +14 -0
  32. data/app/services/api_maker/member_command_service.rb +15 -13
  33. data/app/services/api_maker/model_classes_java_script_generator_service.rb +37 -0
  34. data/app/services/api_maker/model_content_generator_service.rb +17 -21
  35. data/app/services/api_maker/models/save.rb +29 -0
  36. data/app/services/api_maker/models_finder_service.rb +6 -2
  37. data/app/services/api_maker/models_generator_service.rb +6 -43
  38. data/app/services/api_maker/move_components_to_routes.rb +50 -0
  39. data/app/services/api_maker/primary_id_for_model.rb +6 -0
  40. data/app/services/api_maker/reset_indexed_db_service.rb +36 -0
  41. data/app/services/api_maker/routes_file_reloader.rb +20 -0
  42. data/app/services/api_maker/select_columns_on_collection.rb +78 -0
  43. data/app/services/api_maker/select_parser.rb +32 -0
  44. data/app/services/api_maker/service_command.rb +27 -0
  45. data/app/services/api_maker/service_command_service.rb +14 -0
  46. data/app/services/api_maker/simple_model_errors.rb +52 -0
  47. data/app/services/api_maker/update_command.rb +8 -24
  48. data/app/services/api_maker/update_command_service.rb +3 -3
  49. data/app/services/api_maker/valid_command.rb +4 -13
  50. data/app/services/api_maker/valid_command_service.rb +3 -3
  51. data/app/services/api_maker/validation_errors_generator_service.rb +146 -0
  52. data/app/views/api_maker/_data.html.erb +17 -11
  53. data/config/routes.rb +0 -2
  54. data/lib/api_maker/ability.rb +22 -7
  55. data/lib/api_maker/ability_loader.rb +9 -6
  56. data/lib/api_maker/base_collection_instance.rb +15 -0
  57. data/lib/api_maker/base_resource.rb +135 -9
  58. data/lib/api_maker/base_service.rb +14 -0
  59. data/lib/api_maker/collection_serializer.rb +95 -34
  60. data/lib/api_maker/command_spec_helper.rb +41 -11
  61. data/lib/api_maker/configuration.rb +31 -4
  62. data/lib/api_maker/expect_to_able_to_helper.rb +31 -0
  63. data/lib/api_maker/individual_command.rb +24 -9
  64. data/lib/api_maker/javascript/model-template.js.erb +39 -25
  65. data/lib/api_maker/javascript/models.js.erb +6 -0
  66. data/lib/api_maker/loader.rb +1 -1
  67. data/lib/api_maker/memory_storage.rb +1 -1
  68. data/lib/api_maker/model_extensions.rb +34 -18
  69. data/lib/api_maker/permitted_params_argument.rb +5 -1
  70. data/lib/api_maker/preloader.rb +71 -32
  71. data/lib/api_maker/preloader_base.rb +108 -0
  72. data/lib/api_maker/preloader_belongs_to.rb +34 -33
  73. data/lib/api_maker/preloader_has_many.rb +45 -39
  74. data/lib/api_maker/preloader_has_one.rb +30 -47
  75. data/lib/api_maker/railtie.rb +3 -11
  76. data/lib/api_maker/relationship_preloader.rb +42 -0
  77. data/lib/api_maker/resource_routing.rb +18 -4
  78. data/lib/api_maker/result_parser.rb +34 -20
  79. data/lib/api_maker/serializer.rb +53 -22
  80. data/lib/api_maker/spec_helper/browser_logs.rb +14 -0
  81. data/lib/api_maker/spec_helper/execute_collection_command.rb +46 -0
  82. data/lib/api_maker/spec_helper/execute_member_command.rb +52 -0
  83. data/lib/api_maker/spec_helper/expect_no_browser_errors.rb +18 -0
  84. data/lib/api_maker/spec_helper/wait_for_expect.rb +20 -0
  85. data/lib/api_maker/spec_helper/wait_for_flash_message.rb +21 -0
  86. data/lib/api_maker/spec_helper.rb +112 -48
  87. data/lib/api_maker/version.rb +1 -1
  88. data/lib/api_maker.rb +7 -3
  89. metadata +108 -89
  90. data/README.md +0 -476
  91. data/app/controllers/api_maker/devise_controller.rb +0 -60
  92. data/lib/api_maker/base_command.rb +0 -81
  93. data/lib/api_maker/javascript/api.js +0 -92
  94. data/lib/api_maker/javascript/base-model.js +0 -543
  95. data/lib/api_maker/javascript/bootstrap/attribute-row.jsx +0 -16
  96. data/lib/api_maker/javascript/bootstrap/attribute-rows.jsx +0 -47
  97. data/lib/api_maker/javascript/bootstrap/card.jsx +0 -79
  98. data/lib/api_maker/javascript/bootstrap/checkbox.jsx +0 -127
  99. data/lib/api_maker/javascript/bootstrap/checkboxes.jsx +0 -105
  100. data/lib/api_maker/javascript/bootstrap/live-table.jsx +0 -168
  101. data/lib/api_maker/javascript/bootstrap/money-input.jsx +0 -136
  102. data/lib/api_maker/javascript/bootstrap/radio-buttons.jsx +0 -80
  103. data/lib/api_maker/javascript/bootstrap/select.jsx +0 -168
  104. data/lib/api_maker/javascript/bootstrap/string-input.jsx +0 -203
  105. data/lib/api_maker/javascript/cable-connection-pool.js +0 -169
  106. data/lib/api_maker/javascript/cable-subscription-pool.js +0 -111
  107. data/lib/api_maker/javascript/cable-subscription.js +0 -33
  108. data/lib/api_maker/javascript/collection.js +0 -186
  109. data/lib/api_maker/javascript/commands-pool.js +0 -123
  110. data/lib/api_maker/javascript/custom-error.js +0 -14
  111. data/lib/api_maker/javascript/deserializer.js +0 -35
  112. data/lib/api_maker/javascript/devise.js.erb +0 -113
  113. data/lib/api_maker/javascript/error-logger.js +0 -119
  114. data/lib/api_maker/javascript/event-connection.jsx +0 -24
  115. data/lib/api_maker/javascript/event-created.jsx +0 -26
  116. data/lib/api_maker/javascript/event-destroyed.jsx +0 -26
  117. data/lib/api_maker/javascript/event-emitter-listener.jsx +0 -32
  118. data/lib/api_maker/javascript/event-listener.jsx +0 -41
  119. data/lib/api_maker/javascript/event-updated.jsx +0 -26
  120. data/lib/api_maker/javascript/form-data-to-object.js +0 -70
  121. data/lib/api_maker/javascript/included.js +0 -39
  122. data/lib/api_maker/javascript/key-value-store.js +0 -47
  123. data/lib/api_maker/javascript/logger.js +0 -23
  124. data/lib/api_maker/javascript/model-name.js +0 -21
  125. data/lib/api_maker/javascript/models-response-reader.js +0 -43
  126. data/lib/api_maker/javascript/paginate.jsx +0 -128
  127. data/lib/api_maker/javascript/params.js +0 -68
  128. data/lib/api_maker/javascript/resource-route.jsx +0 -75
  129. data/lib/api_maker/javascript/resource-routes.jsx +0 -36
  130. data/lib/api_maker/javascript/result.js +0 -25
  131. data/lib/api_maker/javascript/session-status-updater.js +0 -113
  132. data/lib/api_maker/javascript/sort-link.jsx +0 -88
  133. data/lib/api_maker/javascript/updated-attribute.jsx +0 -60
  134. data/lib/api_maker/preloader_through.rb +0 -101
  135. data/lib/api_maker/relationship_includer.rb +0 -42
@@ -0,0 +1,14 @@
1
+ class ApiMaker::LocalsFromController < ApiMaker::ApplicationService
2
+ attr_reader :controller
3
+
4
+ def initialize(controller:)
5
+ @controller = controller
6
+ end
7
+
8
+ def perform
9
+ variable_name = :@api_maker_locals
10
+ controller.instance_variable_set(variable_name, {}) unless controller.instance_variable_defined?(variable_name)
11
+ variable = controller.instance_variable_get(variable_name)
12
+ succeed! variable
13
+ end
14
+ end
@@ -1,22 +1,24 @@
1
1
  class ApiMaker::MemberCommandService < ApiMaker::CommandService
2
- def execute
3
- ability_name = @command_name.to_sym
4
- collection = model_class.accessible_by(@ability, ability_name).where(model_class.primary_key => ids)
2
+ def perform
3
+ ApiMaker::Configuration.profile(-> { "MemberCommand execute: #{model_class.name}##{command_name}" }) do
4
+ ability_name = command_name.to_sym
5
+ collection = model_class.accessible_by(@ability, ability_name).where(model_class.primary_key => ids)
5
6
 
6
- constant.execute_in_thread!(
7
- ability: ability,
8
- args: args,
9
- collection: collection,
10
- commands: commands,
11
- command_response: command_response,
12
- controller: controller
13
- )
7
+ constant.execute_in_thread!(
8
+ ability: ability,
9
+ api_maker_args: api_maker_args,
10
+ collection: collection,
11
+ commands: commands,
12
+ command_response: command_response,
13
+ controller: controller
14
+ )
14
15
 
15
- ServicePattern::Response.new(success: true)
16
+ succeed!
17
+ end
16
18
  end
17
19
 
18
20
  def constant
19
- @constant ||= "Commands::#{namespace}::#{@command_name.camelize}".constantize
21
+ @constant ||= "Commands::#{namespace}::#{command_name.camelize}".constantize
20
22
  end
21
23
 
22
24
  def ids
@@ -0,0 +1,37 @@
1
+ class ApiMaker::ModelClassesJavaScriptGeneratorService < ApiMaker::ApplicationService
2
+ def perform
3
+ javascript_code = ""
4
+ resource_names = []
5
+
6
+ models.each do |model|
7
+ next if ignore_model?(model)
8
+
9
+ javascript_code << ApiMaker::ModelContentGeneratorService.execute!(model: model)
10
+ javascript_code << "\n\n"
11
+
12
+ resource = resource_for_model(model)
13
+ resource_names << resource.short_name
14
+ end
15
+
16
+ javascript_code << "export {#{resource_names.sort.join(", ")}}\n"
17
+
18
+ succeed! javascript_code
19
+ end
20
+
21
+ def ignore_model?(model)
22
+ model.name.end_with?("::Translation", "::ApplicationRecord")
23
+ end
24
+
25
+ def models
26
+ @models ||= ApiMaker::ModelsFinderService.execute!
27
+ end
28
+
29
+ def model_file(model)
30
+ resource_class = ApiMaker::MemoryStorage.current.resource_for_model(model)
31
+ api_maker_root_path.join("models", "#{resource_class.short_name.underscore.dasherize}.js")
32
+ end
33
+
34
+ def resource_for_model(model)
35
+ ApiMaker::MemoryStorage.current.resource_for_model(model)
36
+ end
37
+ end
@@ -5,11 +5,11 @@ class ApiMaker::ModelContentGeneratorService < ApiMaker::ApplicationService
5
5
  @model = model
6
6
  end
7
7
 
8
- def execute
8
+ def perform
9
9
  if resource
10
- ServicePattern::Response.new(result: model_content)
10
+ succeed! model_content
11
11
  else
12
- ServicePattern::Response.new(errors: ["No resource defined for #{model.name}"])
12
+ fail! "No resource defined for #{model.name}"
13
13
  end
14
14
  end
15
15
 
@@ -17,7 +17,7 @@ private
17
17
 
18
18
  def attributes
19
19
  resource._attributes.map do |attribute, _data|
20
- {name: attribute, type: model_type(attribute)}
20
+ {name: attribute}
21
21
  end
22
22
  end
23
23
 
@@ -41,13 +41,6 @@ private
41
41
  File.join(__dir__, "..", "..", "..", "lib", "api_maker", "javascript", "model-template.js.erb")
42
42
  end
43
43
 
44
- def model_type(attribute_name)
45
- model_type = model.columns_hash[attribute_name.to_s]&.type
46
- model_type = :money if monetized_attributes.include?(attribute_name.to_s)
47
- model_type ||= :unknown
48
- model_type
49
- end
50
-
51
44
  def monetized_attributes
52
45
  @monetized_attributes ||= @model.try(:monetized_attributes).try(:map) { |attribute| attribute[0] } || []
53
46
  end
@@ -55,7 +48,10 @@ private
55
48
  def reflections
56
49
  @reflections ||= resource._relationships.map do |name, _data|
57
50
  reflection = model.reflections.values.find { |reflection_i| reflection_i.name == name }
58
- raise "Couldnt find reflection by that name: #{name}" unless reflection
51
+
52
+ unless reflection
53
+ raise "Couldnt find reflection by that name: #{name} on the model: #{model.name}. Reflections found: #{model.reflections.keys.join(", ")}"
54
+ end
59
55
 
60
56
  reflection
61
57
  end
@@ -66,8 +62,7 @@ private
66
62
  reflectionName: reflection.name,
67
63
  model: "{{this}}",
68
64
  modelName: reflection.class_name,
69
- modelClass: "{{modelClass}}",
70
- targetPathName: "/api_maker/#{reflection.klass.model_name.route_key}"
65
+ modelClass: "{{modelClass}}"
71
66
  }
72
67
  end
73
68
 
@@ -83,21 +78,22 @@ private
83
78
  }
84
79
  }
85
80
  else
86
- {
87
- ransack: {
88
- "#{reflection.foreign_key}_eq" => "{{id}}"
89
- }
90
- }
81
+ ransack = {"#{reflection.foreign_key}_eq" => "{{id}}"}
82
+ ransack["#{reflection.options[:as]}_type_eq"] = reflection.active_record.name if reflection.options[:as]
83
+ {ransack: ransack}
91
84
  end
92
85
  end
93
86
 
94
87
  def reflections_for_model_class_data
95
88
  @reflections_for_model_class_data ||= reflections.map do |reflection|
89
+ resource = ApiMaker::MemoryStorage.current.resource_for_model(reflection.klass)
90
+
96
91
  {
97
92
  className: reflection.class_name,
98
- collectionName: ApiMaker::MemoryStorage.current.resource_for_model(reflection.klass).collection_name,
93
+ collectionName: resource.collection_name,
99
94
  name: reflection.name,
100
- macro: reflection.macro
95
+ macro: reflection.macro,
96
+ resource_name: resource.short_name
101
97
  }
102
98
  end
103
99
  end
@@ -0,0 +1,29 @@
1
+ class ApiMaker::Models::Save < ApiMaker::ApplicationService
2
+ attr_reader :models, :simple_model_errors
3
+
4
+ def initialize(models:, simple_model_errors: false)
5
+ @models = models
6
+ @simple_model_errors = simple_model_errors
7
+ end
8
+
9
+ def perform
10
+ errors = []
11
+
12
+ models.first.transaction do
13
+ models.each do |model|
14
+ next if model.save
15
+
16
+ if simple_model_errors
17
+ errors += ApiMaker::SimpleModelErrors.execute!(model: model)
18
+ else
19
+ errors += models.map(&:errors).map(&:full_messages).flatten
20
+ end
21
+
22
+ raise ActiveRecord::Rollback if errors.any?
23
+ end
24
+ end
25
+
26
+ fail! errors.uniq if errors.any?
27
+ succeed!
28
+ end
29
+ end
@@ -1,5 +1,5 @@
1
1
  class ApiMaker::ModelsFinderService < ApiMaker::ApplicationService
2
- def execute
2
+ def perform
3
3
  ServicePattern::Response.new(result: models)
4
4
  end
5
5
 
@@ -7,7 +7,7 @@ private
7
7
 
8
8
  def models
9
9
  result = []
10
- Dir.glob(Rails.root.join("app", "api_maker", "resources", "**", "*.rb")) do |model_path|
10
+ files.each do |model_path|
11
11
  next unless model_path.start_with?(Rails.root.to_s)
12
12
 
13
13
  path_name = model_path.gsub(/\A#{Regexp.escape(Rails.root.to_s)}\/app\/api_maker\/resources\//, "").gsub(/\.rb\Z/, "")
@@ -19,4 +19,8 @@ private
19
19
 
20
20
  result
21
21
  end
22
+
23
+ def files
24
+ @files ||= Dir.glob(Rails.root.join("app/api_maker/resources/**/*_resource.rb"))
25
+ end
22
26
  end
@@ -1,29 +1,14 @@
1
1
  class ApiMaker::ModelsGeneratorService < ApiMaker::ApplicationService
2
- def execute
2
+ def perform
3
3
  create_base_structure
4
4
  copy_base_model
5
- copy_base_controllers
6
-
7
- models.each do |model|
8
- next if ignore_model?(model)
9
-
10
- model_content_response = ApiMaker::ModelContentGeneratorService.execute(model: model)
11
-
12
- if model_content_response.success?
13
- File.open(model_file(model), "w") { |fp| fp.write(model_content_response.result) }
14
- else
15
- puts model_content_response.errors.join(". ")
16
- end
17
- end
18
5
 
19
6
  ApiMaker::GenerateReactNativeApiService.execute! if ApiMaker::Configuration.current.react_native_path.present?
20
- ServicePattern::Response.new(success: true)
7
+ succeed!
21
8
  end
22
9
 
23
10
  def ignore_model?(model)
24
- model.name.end_with?("::Translation") ||
25
- model.name.start_with?("ActiveStorage::") ||
26
- model.name.end_with?("::ApplicationRecord")
11
+ model.name.end_with?("::Translation", "::ApplicationRecord")
27
12
  end
28
13
 
29
14
  def models
@@ -33,31 +18,15 @@ class ApiMaker::ModelsGeneratorService < ApiMaker::ApplicationService
33
18
  private
34
19
 
35
20
  def api_maker_root_path
36
- Rails.root.join("app", "javascript", "api-maker")
21
+ Rails.root.join("app/javascript/api-maker")
37
22
  end
38
23
 
39
24
  def controller_path
40
- Rails.root.join("app", "controllers", "api_maker")
41
- end
42
-
43
- def copy_base_controllers
44
- files = %w[devise_controller.rb]
45
- path = File.join(__dir__, "..", "..", "controllers", "api_maker")
46
- target_path = Rails.root.join("app", "controllers", "api_maker")
47
-
48
- copy_base_files(files, path, target_path)
25
+ Rails.root.join("app/controllers/api_maker")
49
26
  end
50
27
 
51
28
  def copy_base_model
52
- files = %w[
53
- api.js base-model.js cable-connection-pool.js cable-subscription.js cable-subscription-pool.js collection.js
54
- commands-pool.js event-created.jsx custom-error.js deserializer.js devise.js event-destroyed.jsx event-emitter-listener.jsx event-listener.jsx
55
- event-updated.jsx error-logger.js form-data-to-object.js included.js key-value-store.js logger.js model-name.js models-response-reader.js
56
- params.js result.js event-connection.jsx paginate.jsx sort-link.jsx updated-attribute.jsx resource-routes.jsx resource-route.jsx
57
- session-status-updater.js bootstrap/attribute-row.jsx bootstrap/attribute-rows.jsx bootstrap/card.jsx
58
- bootstrap/checkbox.jsx bootstrap/checkboxes.jsx bootstrap/money-input.jsx bootstrap/radio-buttons.jsx
59
- bootstrap/select.jsx bootstrap/string-input.jsx bootstrap/live-table.jsx
60
- ]
29
+ files = %w[models.js.erb]
61
30
  path = File.join(__dir__, "..", "..", "..", "lib", "api_maker", "javascript")
62
31
  target_path = api_maker_root_path
63
32
 
@@ -93,12 +62,6 @@ private
93
62
  # Dont remove all the files. It messes up running Webpack Dev Servers which forces you to restart all the time.
94
63
  # FileUtils.rm_rf(api_maker_root_path) if File.exist?(api_maker_root_path)
95
64
 
96
- FileUtils.mkdir_p(api_maker_root_path.join("models"))
97
65
  FileUtils.mkdir_p(controller_path) unless File.exist?(controller_path)
98
66
  end
99
-
100
- def model_file(model)
101
- resource_class = ApiMaker::MemoryStorage.current.resource_for_model(model)
102
- api_maker_root_path.join("models", "#{resource_class.short_name.underscore.dasherize}.js")
103
- end
104
67
  end
@@ -0,0 +1,50 @@
1
+ class ApiMaker::MoveComponentsToRoutes < ApplicationService
2
+ attr_reader :prepend_path, :routes_path
3
+
4
+ def initialize(prepend_path:, routes_path:)
5
+ @prepend_path = prepend_path
6
+ @routes_path = routes_path
7
+ end
8
+
9
+ def perform
10
+ routes.each do |route|
11
+ path = "#{prepend_path}#{route.fetch("component")}"
12
+ full_path = "app/javascript/components/#{path}"
13
+ new_path = "app/javascript/routes/#{path}"
14
+
15
+ full_path_with_jsx = "#{full_path}.jsx"
16
+ new_path_with_jsx = "#{new_path}.jsx"
17
+
18
+ if !File.exist?(full_path_with_jsx) && File.exist?("#{full_path}/index.jsx")
19
+ full_path_with_jsx = "#{full_path}/index.jsx"
20
+ new_path_with_jsx = "#{new_path}/index.jsx"
21
+ end
22
+
23
+ if File.exist?(full_path_with_jsx)
24
+ new_dir = File.dirname(new_path_with_jsx)
25
+ FileUtils.mkdir_p(new_dir) unless File.exist?(new_dir)
26
+ File.rename(full_path_with_jsx, new_path_with_jsx)
27
+ end
28
+
29
+ old_dir = File.dirname(full_path_with_jsx)
30
+
31
+ Dir.unlink(old_dir) if File.exist?(old_dir) && dir_empty?(old_dir)
32
+ end
33
+
34
+ succeed!
35
+ end
36
+
37
+ def routes
38
+ @routes = JSON.parse(File.read(routes_path)).fetch("routes")
39
+ end
40
+
41
+ def dir_empty?(path)
42
+ Dir.foreach(path) do |file|
43
+ next if file == "." || file == ".."
44
+
45
+ return false
46
+ end
47
+
48
+ true
49
+ end
50
+ end
@@ -0,0 +1,6 @@
1
+ class ApiMaker::PrimaryIdForModel
2
+ # Hack to get the primary ID or fail to make up for if the attribute wasn't selected
3
+ def self.get(model)
4
+ model.id || raise("No attribute called '#{model.class.primary_key}' given on #{model.class.name}")
5
+ end
6
+ end
@@ -0,0 +1,36 @@
1
+ class ApiMaker::ResetIndexedDbService < ApiMaker::ApplicationService
2
+ attr_reader :context
3
+
4
+ delegate :browser_firefox?, :browser_logs, :execute_script, to: :context
5
+
6
+ def initialize(context:)
7
+ @context = context
8
+ end
9
+
10
+ def perform
11
+ # Firefox doesnt support 'indexedDB.databases()'
12
+ reset_indexed_db unless browser_firefox?
13
+
14
+ succeed!
15
+ end
16
+
17
+ def reset_indexed_db
18
+ execute_script "
19
+ indexedDB.databases().then(function(databases) {
20
+ var promises = []
21
+ for(var database of databases) {
22
+ promises.push(indexedDB.deleteDatabase(database.name))
23
+ }
24
+
25
+ Promise.all(promises).then(function() {
26
+ console.error('All databases were deleted')
27
+ })
28
+ })
29
+ "
30
+
31
+ WaitUtil.wait_for_condition("databases to be deleted", delay_sec: 0.2, timeout_sec: 6) do
32
+ logs_text = browser_logs.map(&:message).join("\n")
33
+ logs_text.include?("\"All databases were deleted\"")
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,20 @@
1
+ class ApiMaker::RoutesFileReloader < ApiMaker::ApplicationService
2
+ attr_reader :file_paths
3
+
4
+ def initialize(file_paths:)
5
+ @file_paths = file_paths
6
+ end
7
+
8
+ def perform
9
+ reloader = Rails.application.config.file_watcher.new(file_paths) do
10
+ Rails.application.reload_routes!
11
+ end
12
+
13
+ Rails.application.reloaders << reloader
14
+ Rails.application.reloader.to_run do
15
+ reloader.execute_if_updated
16
+ end
17
+
18
+ succeed!
19
+ end
20
+ end
@@ -0,0 +1,78 @@
1
+ class ApiMaker::SelectColumnsOnCollection < ApiMaker::ApplicationService
2
+ attr_reader :collection, :model_class, :select_attributes, :select_columns, :table_name
3
+
4
+ def initialize(collection:, model_class: nil, select_attributes:, select_columns:, table_name: nil)
5
+ raise "No collection was given" unless collection
6
+
7
+ @collection = collection
8
+ @model_class = model_class || collection.model
9
+ @select_attributes = select_attributes&.dig(@model_class)
10
+ @select_columns = select_columns
11
+ @table_name = table_name || @model_class.table_name
12
+ end
13
+
14
+ def perform
15
+ new_collection = collection
16
+ return succeed! new_collection if new_collection.is_a?(Array)
17
+
18
+ arel_table = Arel::Table.new(@table_name)
19
+
20
+ if selected_columns
21
+ all_columns_to_select.each do |column_name|
22
+ validate_column_name!(column_name)
23
+ new_collection = new_collection.select(arel_table[column_name])
24
+ end
25
+ else
26
+ new_collection = prepend_table_wildcard(new_collection) unless table_wildcard_prepended?(new_collection)
27
+ end
28
+
29
+ succeed! new_collection
30
+ end
31
+
32
+ # Selected attributes might require columns to be selected which is automatically resolved here
33
+ def all_columns_to_select
34
+ required_columns = []
35
+
36
+ select_attributes&.each_value do |attribute_args|
37
+ requires_columns = attribute_args.dig(:args, :requires_columns)
38
+ required_columns += requires_columns if requires_columns
39
+ end
40
+
41
+ (selected_columns + required_columns).map(&:to_s).uniq
42
+ end
43
+
44
+ def param_name
45
+ @param_name ||= model_class.model_name.param_key
46
+ end
47
+
48
+ # Prepends 'table_name.*' to the query. It needs to be pre-pended in case a `COUNT` or another aggregate function has been added to work with `DISTINCT`.
49
+ def prepend_table_wildcard(query)
50
+ old_select = query.values[:select] || []
51
+ old_select = old_select.keep_if { |select_statement| select_statement != select_table_wildcard_sql }
52
+
53
+ query = query.except(:select).select(select_table_wildcard_sql)
54
+
55
+ old_select.each do |select_statement|
56
+ query = query.select(select_statement)
57
+ end
58
+
59
+ query
60
+ end
61
+
62
+ def selected_columns
63
+ @selected_columns ||= select_columns&.dig(param_name)
64
+ end
65
+
66
+ def select_table_wildcard_sql
67
+ @select_table_wildcard_sql ||= "#{table_name}.*"
68
+ end
69
+
70
+ def table_wildcard_prepended?(query)
71
+ query.values[:select]&.first == select_table_wildcard_sql
72
+ end
73
+
74
+ # Checks that the given column exists to avoid injections
75
+ def validate_column_name!(column_name)
76
+ raise "Invalid column on #{model_class.name}: #{column_name}" unless model_class.columns_hash.key?(column_name.to_s)
77
+ end
78
+ end
@@ -0,0 +1,32 @@
1
+ class ApiMaker::SelectParser < ApiMaker::ApplicationService
2
+ attr_reader :select
3
+
4
+ def initialize(select:)
5
+ @select = select
6
+ end
7
+
8
+ def perform
9
+ new_select = {}
10
+
11
+ select.each do |model_collection_name, attributes|
12
+ model_class = model_collection_name.underscore.singularize.camelize
13
+ resource = "Resources::#{model_class}Resource".safe_constantize
14
+ raise "Resource not found for: #{model_collection_name}" unless resource
15
+
16
+ selects = {}
17
+ new_select[resource.model_class] ||= selects
18
+ resource_attributes = resource._attributes_with_string_keys
19
+
20
+ attributes.each do |attribute|
21
+ resource_attribute = resource_attributes[attribute]
22
+ raise "Attribute not found on the #{resource.short_name} resource: #{attribute}" unless resource_attribute
23
+
24
+ selects[attribute.to_sym] = resource_attribute
25
+ end
26
+
27
+ new_select[resource.model_class] = selects
28
+ end
29
+
30
+ succeed! new_select
31
+ end
32
+ end
@@ -0,0 +1,27 @@
1
+ class ApiMaker::ServiceCommand < ApiMaker::BaseCommand
2
+ def execute!
3
+ service_name = args.fetch(:service_name)
4
+
5
+ ApiMaker::Configuration.profile(-> { "ServiceCommand: #{service_name}" }) do
6
+ service_constant_name = "Services::#{service_name}"
7
+ service_constant = service_constant_name.constantize
8
+ service_args = args.fetch(:service_args)&.permit!&.to_h
9
+ response = service_constant.execute(
10
+ ability: current_ability,
11
+ args: service_args,
12
+ api_maker_args: api_maker_args,
13
+ controller: controller
14
+ )
15
+
16
+ if response.success?
17
+ succeed!(response.result)
18
+ else
19
+ errors = response.errors.map do |error|
20
+ {message: error.message, type: error.type}
21
+ end
22
+
23
+ fail!(errors: errors)
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,14 @@
1
+ class ApiMaker::ServiceCommandService < ApiMaker::CommandService
2
+ def perform
3
+ ApiMaker::ServiceCommand.execute_in_thread!(
4
+ ability: ability,
5
+ api_maker_args: api_maker_args,
6
+ collection: nil,
7
+ commands: commands,
8
+ command_response: command_response,
9
+ controller: controller
10
+ )
11
+
12
+ succeed!
13
+ end
14
+ end
@@ -0,0 +1,52 @@
1
+ class ApiMaker::SimpleModelErrors < ApiMaker::ApplicationService
2
+ attr_reader :additional_attributes, :errors, :model, :models_inspected
3
+
4
+ def initialize(additional_attributes: [], model:)
5
+ @additional_attributes = additional_attributes
6
+ @model = model
7
+ @errors = []
8
+ @models_inspected = []
9
+ end
10
+
11
+ def perform
12
+ inspect_model(model)
13
+ succeed! errors
14
+ end
15
+
16
+ def inspect_model(model_to_inspect)
17
+ return if models_inspected.include?(model_to_inspect)
18
+
19
+ model_to_inspect.valid? if model_to_inspect.errors.empty? # Generates the errors on the model so we can detect them
20
+ models_inspected << model_to_inspect
21
+
22
+ model_to_inspect.errors.messages.each do |attribute_name, attribute_errors|
23
+ if attribute_name == :base
24
+ @errors += attribute_errors
25
+ else
26
+ next if model_to_inspect.attribute_names.exclude?(attribute_name.to_s) && additional_attributes.exclude?(attribute_name)
27
+
28
+ attribute_errors.each do |message|
29
+ errors << "#{model_to_inspect.class.human_attribute_name(attribute_name)} #{message}"
30
+ end
31
+ end
32
+ end
33
+
34
+ collect_errors_from_associations(model_to_inspect)
35
+ end
36
+
37
+ private
38
+
39
+ def collect_errors_from_associations(model_to_scan_reflections_on)
40
+ model_to_scan_reflections_on._reflections.each_key do |association_name|
41
+ target = model_to_scan_reflections_on.association(association_name.to_sym).target
42
+
43
+ if target.is_a?(ActiveRecord::Base)
44
+ inspect_model(target)
45
+ else
46
+ target&.each do |sub_model|
47
+ inspect_model(sub_model)
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end