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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7d65964fbfeb5bd8a5d89702083930f77e3d9c46956695c4f0877039a43c3010
4
- data.tar.gz: 2a9431af5f0bfebcfb9605d85fafc40b824448aeac9ccd5a0f595b8e9f6d642d
3
+ metadata.gz: 696d6f1f8b6baa82e326b7d05f4e6f6fdd57de5a7ad9a20d7961d1d7ec361e78
4
+ data.tar.gz: 8960e2876a0c29d700c9c429e0153f43ea31e0839a03d423ffeeb92863f18ee2
5
5
  SHA512:
6
- metadata.gz: bf2858ef950edf1aa8cc9ce45938ebd1b5ea5f3dd1f11223fd42959bff910a5cceae21599abbfdce7dbabedcdcd481c54c832c77712417436d02f423285fb48d
7
- data.tar.gz: 7f63b82811ef1a5207d2ecbdcc62072b4d4297263884de1c4b36704e7b114a2d027cbbb3dac324de69af6c19fc79f185f273b4bf070def5f7c9282e1b1caa404
6
+ metadata.gz: c185f283cb899f81293df2e93e91c9298a4c1bfe5abbc5eb56f4fd23444ff46ce42776718cc2547e103ff511e30406fdb62761363813c68d9e67375cbcee2363
7
+ data.tar.gz: 40be89f333195778c4c7455d4f76ec85a1786637346e75f91dd043296870d79f340b53193bf28420962c6f7f418858697e4daa0e803684d29af20ebb6481076a
@@ -0,0 +1,5 @@
1
+ module ApiHelpers::ApiMakerHelpers
2
+ def locals
3
+ @locals ||= ApiMaker::LocalsFromController.execute!(controller: controller)
4
+ end
5
+ end
@@ -0,0 +1,30 @@
1
+ class Services::CanCan::LoadAbilities < ApiMaker::BaseService
2
+ def perform
3
+ result = []
4
+
5
+ request.each do |ability_data|
6
+ # Sometimes Rails passes a hash instead of an array
7
+ ability_data = ability_data.fetch(1) if ability_data.is_a?(Array)
8
+
9
+ ability = ability_data.fetch("ability")
10
+ subject = ability_data.fetch("subject")
11
+ subject_to_check = subject
12
+
13
+ # Convert subject to original model class if resource is given
14
+ subject_to_check = subject.model_class if subject.is_a?(Class) && subject < ApiMaker::BaseResource
15
+
16
+ can = current_ability.can?(ability.to_sym, subject_to_check)
17
+ result << {
18
+ ability: ability,
19
+ can: can,
20
+ subject: subject
21
+ }
22
+ end
23
+
24
+ succeed!(abilities: result)
25
+ end
26
+
27
+ def request
28
+ @request ||= args.fetch(:request)
29
+ end
30
+ end
@@ -0,0 +1,64 @@
1
+ class Services::Devise::SignIn < ApiMaker::BaseService
2
+ include Devise::Controllers::Rememberable
3
+
4
+ # Rememberable needs this
5
+ def cookies
6
+ controller.__send__(:cookies)
7
+ end
8
+
9
+ def perform
10
+ fail! "Devise sign in isn't enabled", type: :devise_sign_in_isnt_enabled unless ApiMaker::Configuration.current.devise_sign_in_enabled
11
+
12
+ check_model_exists
13
+ check_serializer_exists
14
+
15
+ if !model.active_for_authentication?
16
+ fail! inactive_message, type: :inactive
17
+ elsif model.valid_password?(args[:password])
18
+ controller.sign_in(model, scope: scope)
19
+ remember_me(model) if args.dig(:args, :rememberMe)
20
+ succeed!(model_data: serializer.result)
21
+ else
22
+ fail! invalid_error_message, type: :invalid
23
+ end
24
+ end
25
+
26
+ def check_model_exists
27
+ error_msg = I18n.t("devise.failure.not_found_in_database", authentication_keys: model_class.authentication_keys.join(", "))
28
+ fail! error_msg, type: :not_found_in_database unless model
29
+ end
30
+
31
+ def check_serializer_exists
32
+ fail! "Serializer doesn't exist for #{scope}", type: :serializer_doesnt_exist unless resource
33
+ end
34
+
35
+ def inactive_message
36
+ message = model.inactive_message
37
+ message = I18n.t("devise.failure.#{message}") if message.is_a?(Symbol)
38
+ message
39
+ end
40
+
41
+ def invalid_error_message
42
+ I18n.t("devise.failure.invalid", authentication_keys: model_class.authentication_keys.join(", "))
43
+ end
44
+
45
+ def model
46
+ @model ||= model_class.find_for_authentication(email: args[:username])
47
+ end
48
+
49
+ def model_class
50
+ @model_class ||= scope.camelize.safe_constantize
51
+ end
52
+
53
+ def scope
54
+ @scope ||= args.dig(:args, :scope).presence || "user"
55
+ end
56
+
57
+ def resource
58
+ @resource ||= ApiMaker::Serializer.resource_for(model.class)
59
+ end
60
+
61
+ def serializer
62
+ @serializer ||= ApiMaker::Serializer.new(ability: current_ability, api_maker_args: api_maker_args, model: model)
63
+ end
64
+ end
@@ -0,0 +1,9 @@
1
+ class Services::Devise::SignOut < ApiMaker::BaseService
2
+ def perform
3
+ fail! "Devise sign out isn't enabled", type: :devise_sign_out_isnt_enabled unless ApiMaker::Configuration.current.devise_sign_out_enabled
4
+ scope = args.dig(:args, :scope).presence || "user"
5
+ current_model = controller.__send__("current_#{scope}")
6
+ controller.sign_out current_model
7
+ succeed!(success: true)
8
+ end
9
+ end
@@ -0,0 +1,18 @@
1
+ class Services::Models::FindOrCreateBy < ApiMaker::BaseService
2
+ def perform
3
+ resource_name = args.fetch(:resource_name)
4
+ resource = "Resources::#{resource_name}Resource".safe_constantize
5
+ model_class = resource.model_class
6
+ find_or_create_by_args = args.fetch(:find_or_create_by_args)
7
+ additional_data = args[:additional_data]
8
+
9
+ model = model_class.find_or_initialize_by(find_or_create_by_args)
10
+ model.assign_attributes(additional_data) if model.new_record? && additional_data
11
+
12
+ if model.save
13
+ succeed! model: model
14
+ else
15
+ fail! errors: model.errors.full_messages
16
+ end
17
+ end
18
+ end
@@ -1,6 +1,10 @@
1
1
  class ApiMaker::SubscriptionsChannel < ApplicationCable::Channel
2
2
  def subscribed
3
3
  params[:subscription_data].each do |model_name, subscription_types|
4
+ subscription_types["model_class_events"]&.each do |event_name|
5
+ connect_model_class_event(model_name, event_name)
6
+ end
7
+
4
8
  subscription_types["events"]&.each do |event_name, model_ids|
5
9
  connect_event(model_name, model_ids, event_name)
6
10
  end
@@ -25,16 +29,24 @@ private
25
29
  model_class = model_for_resource_name(model_name)
26
30
  channel_name = model_class.api_maker_broadcast_create_channel_name
27
31
  stream_from(channel_name, coder: ActiveSupport::JSON) do |data|
32
+ ApiMaker::Configuration.current.before_create_event_callbacks.each do |callback|
33
+ callback.call(data: data)
34
+ end
35
+
28
36
  # We need to look the model up to evaluate if the user has access
29
- model = data.fetch("model_class_name").safe_constantize.accessible_by(current_ability, :create_events).find(data.fetch("model_id"))
37
+ model_class = data.fetch("mcn").safe_constantize
30
38
 
31
- # Transmit the data to JS if its allowed
39
+ Rails.logger.debug { "API maker: ConnectCreates for #{model_class.name}" }
40
+ model = model_class.accessible_by(current_ability, :create_events).find_by(model_class.primary_key => data.fetch("mi"))
41
+
42
+ # Transmit the data to JS if its found (and thereby allowed)
32
43
  transmit data if model
33
44
  end
34
45
  end
35
46
 
36
47
  def connect_destroys(model_name, model_ids)
37
48
  model_class = model_for_resource_name(model_name)
49
+ Rails.logger.debug { "API maker: ConnectDestroys for #{model_class.name}" }
38
50
  models = model_class.accessible_by(current_ability, :destroy_events).where(model_class.primary_key => model_ids)
39
51
  models.each do |model|
40
52
  channel_name = model.api_maker_broadcast_destroy_channel_name
@@ -47,6 +59,7 @@ private
47
59
  def connect_event(model_name, model_ids, event_name)
48
60
  ability_name = "event_#{event_name}".to_sym
49
61
  model_class = model_for_resource_name(model_name)
62
+ Rails.logger.debug { "API maker: ConnectEvents for #{model_class.name} #{event_name}" }
50
63
  models = model_class.accessible_by(current_ability, ability_name).where(model_class.primary_key => model_ids)
51
64
  models.each do |model|
52
65
  channel_name = model.api_maker_event_channel_name(event_name)
@@ -56,8 +69,26 @@ private
56
69
  end
57
70
  end
58
71
 
72
+ def connect_model_class_event(model_name, event_name)
73
+ ability_name = "model_class_event_#{event_name}".to_sym
74
+ model_class = model_for_resource_name(model_name)
75
+ channel_name = model_class.api_maker_model_class_event_name(event_name)
76
+
77
+ Rails.logger.debug { "API maker: ConnectModelClassEvent for #{model_class.name} #{event_name}" }
78
+
79
+ if current_ability.can?(ability_name, model_class)
80
+ stream_from(channel_name, coder: ActiveSupport::JSON) do |data|
81
+ transmit data
82
+ end
83
+ else
84
+ Rails.logger.warn { "API maker: No access to model class event #{model_class.name}##{event_name} with ability name: #{ability_name}" }
85
+ end
86
+ end
87
+
59
88
  def connect_updates(model_name, model_ids)
60
89
  model_class = model_for_resource_name(model_name)
90
+ Rails.logger.debug { "API maker: ConnectUpdates for #{model_class.name}" }
91
+
61
92
  models = model_class.accessible_by(current_ability, :update_events).where(model_class.primary_key => model_ids)
62
93
  models.each do |model|
63
94
  channel_name = model.api_maker_broadcast_update_channel_name
@@ -8,15 +8,19 @@ class ApiMaker::BaseController < ApplicationController
8
8
  private
9
9
 
10
10
  def current_ability
11
- @current_ability ||= ::ApiMaker::Ability.new(args: api_maker_args)
11
+ @current_ability ||= ApiMaker::Configuration.current.ability_class.new(api_maker_args: api_maker_args, locals: api_maker_locals)
12
+ end
13
+
14
+ def api_maker_locals
15
+ @api_maker_locals ||= {}
12
16
  end
13
17
 
14
18
  def render_error(error)
15
19
  puts error.inspect
16
- puts error.backtrace
20
+ puts Rails.backtrace_cleaner.clean(error.backtrace)
17
21
 
18
22
  logger.error error.inspect
19
- logger.error error.backtrace.join("\n")
23
+ logger.error Rails.backtrace_cleaner.clean(error.backtrace).join("\n")
20
24
 
21
25
  raise error
22
26
  end
@@ -1,19 +1,21 @@
1
1
  class ApiMaker::CommandsController < ApiMaker::BaseController
2
+ wrap_parameters false
3
+
2
4
  def create
3
5
  command_response = ApiMaker::CommandResponse.new(controller: self)
4
6
  controller = self
5
7
 
6
- params[:pool].each do |command_type, command_type_data|
8
+ merged_params.fetch(:pool).each do |command_type, command_type_data|
7
9
  command_type_data.each do |resource_plural_name, command_model_data|
8
10
  command_model_data.each do |command_name, command_data|
9
11
  ApiMaker.const_get("#{command_type.camelize}CommandService").execute!(
10
12
  ability: current_ability,
11
- args: api_maker_args,
13
+ api_maker_args: api_maker_args,
12
14
  command_response: command_response,
13
15
  commands: command_data,
14
16
  command_name: command_name,
15
- resource_name: resource_plural_name,
16
- controller: controller
17
+ controller: controller,
18
+ resource_name: resource_plural_name
17
19
  )
18
20
  end
19
21
  end
@@ -23,4 +25,24 @@ class ApiMaker::CommandsController < ApiMaker::BaseController
23
25
 
24
26
  render json: {responses: command_response.result}
25
27
  end
28
+
29
+ def json_params
30
+ @json_params ||= ActionController::Parameters.new(json_data) if json_data
31
+ end
32
+
33
+ def json_data
34
+ @json_data ||= JSON.parse(params[:json]) if params[:json]
35
+ end
36
+
37
+ def merged_params
38
+ @merged_params ||= if json_data
39
+ raw_data = params.permit!.to_h
40
+ merged_data = ApiMaker::DeepMergeParams.execute!(json_data, raw_data)
41
+ ActionController::Parameters.new(merged_data)
42
+ elsif json_params
43
+ json_params
44
+ else
45
+ params
46
+ end
47
+ end
26
48
  end
@@ -1,4 +1,4 @@
1
- class ApiMaker::SessionStatusesController < ActionController::Base
1
+ class ApiMaker::SessionStatusesController < ActionController::Base # rubocop:disable Rails/ApplicationController
2
2
  skip_before_action :verify_authenticity_token, raise: false
3
3
 
4
4
  def create
@@ -0,0 +1,104 @@
1
+ class ApiMaker::AbilitiesLoader < ApiMaker::ApplicationService
2
+ attr_reader :abilities, :abilities_data, :abilities_with_no_conditions, :abilities_with_no_rules, :ability, :groupings, :serializers
3
+
4
+ def initialize(abilities:, ability:, serializers:)
5
+ @ability = ability
6
+ @abilities = abilities.map(&:to_sym)
7
+ @abilities_data = {}
8
+ @abilities_with_no_conditions = []
9
+ @abilities_with_no_rules = []
10
+ @groupings = {}
11
+ @serializers = serializers
12
+ end
13
+
14
+ def perform
15
+ scan_abilities
16
+ scan_groupings
17
+
18
+ load_abilities_with_no_conditions if abilities_with_no_conditions.any?
19
+ load_abilities_with_no_rules if abilities_with_no_rules.any?
20
+
21
+ groupings.each_value do |ability_names|
22
+ load_abilities_with_conditions(ability_names)
23
+ end
24
+
25
+ succeed!
26
+ end
27
+
28
+ def scan_abilities
29
+ abilities.each do |ability_name|
30
+ abilities_data[ability_name] = {
31
+ combined_conditions: [],
32
+ rules_count: 0,
33
+ rule_with_no_conditions: false
34
+ }
35
+
36
+ relevant_rules = ability.__send__(:relevant_rules, ability_name, model_class)
37
+ relevant_rules.each do |can_can_rule|
38
+ abilities_data[ability_name][:combined_conditions] << can_can_rule.conditions.to_s
39
+ abilities_data[ability_name][:rules_count] += 1
40
+
41
+ if can_can_rule.__send__(:conditions_empty?)
42
+ abilities_data[ability_name][:rule_with_no_conditions] = true
43
+ abilities_with_no_conditions << ability_name
44
+ end
45
+ end
46
+ end
47
+ end
48
+
49
+ def scan_groupings
50
+ abilities_data.each do |ability_name, ability_data|
51
+ # No reason to create a group with no conditions - we are giving access to these without doing a query elsewhere
52
+ next if ability_data.fetch(:rule_with_no_conditions)
53
+
54
+ # If no rules have been defined, then we can safely assume no access without doing a query elsewhere
55
+ if ability_data.fetch(:rules_count).zero?
56
+ abilities_with_no_rules << ability_name
57
+ else
58
+ identifier = ability_data[:combined_conditions].join("___")
59
+
60
+ groupings[identifier] ||= []
61
+ groupings[identifier] << ability_name
62
+ end
63
+ end
64
+
65
+ groupings
66
+ end
67
+
68
+ def load_abilities_with_no_conditions
69
+ Rails.logger.debug { "API maker: Loading abilities with no condition: [#{abilities_with_no_conditions.join(", ")}], #{model_class}" }
70
+ abilities_with_no_conditions.each do |ability_name|
71
+ serializers.each do |serializer|
72
+ serializer.load_ability(ability_name, true)
73
+ end
74
+ end
75
+ end
76
+
77
+ def load_abilities_with_no_rules
78
+ Rails.logger.debug { "API maker: Loading abilities with no rules: [#{abilities_with_no_rules.join(", ")}], #{model_class}" }
79
+ abilities_with_no_rules.each do |ability_name|
80
+ serializers.each do |serializer|
81
+ serializer.load_ability(ability_name, false)
82
+ end
83
+ end
84
+ end
85
+
86
+ def load_abilities_with_conditions(ability_names)
87
+ Rails.logger.debug { "API maker: Loading abilities with conditions through query: [#{ability_names.join(", ")}], #{model_class}" }
88
+
89
+ model_ids = model_class
90
+ .accessible_by(ability, ability_names.first)
91
+ .where(id: serializers.map(&:id))
92
+ .pluck(model_class.primary_key)
93
+
94
+ serializers.each do |serializer|
95
+ ability_names.each do |ability_name|
96
+ serializer.load_ability(ability_name, model_ids.include?(serializer.id))
97
+ end
98
+ end
99
+ end
100
+
101
+ def model_class
102
+ @model_class ||= serializers.first.model.class
103
+ end
104
+ end
@@ -1,7 +1,8 @@
1
1
  class ApiMaker::ApplicationService < ServicePattern::Service
2
+ # Replaces magic variables with actual variable names
2
3
  def api_maker_json(object)
3
4
  json = object.to_json
4
- json.gsub!(/"\{\{(.+?)\}\}"/, "\\1")
5
+ json.gsub!(/"\{\{([A-z_]+?)\}\}"/, "\\1")
5
6
  json
6
7
  end
7
8
  end
@@ -0,0 +1,248 @@
1
+ class ApiMaker::BaseCommand
2
+ ApiMaker::IncludeHelpers.execute!(klass: self)
3
+
4
+ attr_reader :api_maker_args, :collection, :collection_instance, :command, :commands, :command_response, :controller, :current_ability
5
+
6
+ delegate :args, :model, :model_id, to: :command
7
+ delegate :result_for_command, to: :command_response
8
+
9
+ # Returns true if the gem "goldiloader" is present in the app
10
+ def self.goldiloader?
11
+ @goldiloader = Gem::Specification.find_all_by_name("goldiloader").any? if @goldiloader.nil?
12
+ @goldiloader
13
+ end
14
+
15
+ def initialize(ability:, api_maker_args:, collection:, collection_instance:, command:, commands:, command_response:, controller:)
16
+ @api_maker_args = api_maker_args
17
+ @current_ability = ability
18
+ @collection = collection
19
+ @collection_instance = collection_instance
20
+ @command = command
21
+ @commands = commands
22
+ @command_response = command_response
23
+ @controller = controller
24
+ end
25
+
26
+ def execute_with_response
27
+ execute!
28
+ rescue ApiMaker::CommandFailedError => e
29
+ command.fail(*e.api_maker_args, &e.api_maker_block)
30
+ end
31
+
32
+ def self.command_error_message(error)
33
+ if Rails.application.config.consider_all_requests_local
34
+ "#{error.class.name}: #{error.message}"
35
+ else
36
+ "Internal server error"
37
+ end
38
+ end
39
+
40
+ def self.execute_in_thread!(ability:, api_maker_args:, collection:, commands:, command_response:, controller:)
41
+ command_response.with_thread do
42
+ if const_defined?(:CollectionInstance)
43
+ collection_instance = const_get(:CollectionInstance).new(
44
+ ability: ability,
45
+ api_maker_args: api_maker_args,
46
+ collection: collection,
47
+ commands: commands,
48
+ command_response: command_response,
49
+ controller: controller
50
+ )
51
+
52
+ collection = collection_instance.custom_collection if collection_instance.respond_to?(:custom_collection)
53
+ collection_instance.collection = collection
54
+
55
+ threadded = collection_instance.try(:threadded?)
56
+ end
57
+
58
+ if threadded
59
+ # Goldiloader doesn't work with threads (loads all relationships for each thread)
60
+ collection = collection.auto_include(false) if ApiMaker::BaseCommand.goldiloader?
61
+
62
+ # Load relationship before commands so each command doesn't query on its own
63
+ collection.load
64
+ end
65
+
66
+ each_command(collection: collection, command_response: command_response, commands: commands, controller: controller, threadded: threadded) do |command|
67
+ command_instance = new(
68
+ ability: ability,
69
+ api_maker_args: api_maker_args,
70
+ collection: collection,
71
+ collection_instance: collection_instance,
72
+ command: command,
73
+ commands: command,
74
+ command_response: command_response,
75
+ controller: controller
76
+ )
77
+ command_instance.execute_with_response
78
+ end
79
+ end
80
+ end
81
+
82
+ def self.each_command(collection:, command_response:, commands:, controller:, threadded:, &blk)
83
+ commands.each do |command_id, command_data|
84
+ if threadded
85
+ command_response.with_thread do
86
+ run_command(
87
+ command_id: command_id,
88
+ command_data: command_data,
89
+ command_response: command_response,
90
+ collection: collection,
91
+ controller: controller,
92
+ &blk
93
+ )
94
+ end
95
+ else
96
+ run_command(
97
+ command_id: command_id,
98
+ command_data: command_data,
99
+ command_response: command_response,
100
+ collection: collection,
101
+ controller: controller,
102
+ &blk
103
+ )
104
+ end
105
+ end
106
+ end
107
+
108
+ def self.run_command(collection:, command_id:, command_data:, command_response:, controller:)
109
+ command = ApiMaker::IndividualCommand.new(
110
+ args: ApiMaker::Deserializer.execute!(arg: command_data[:args]),
111
+ collection: collection,
112
+ command: self,
113
+ id: command_id,
114
+ primary_key: command_data[:primary_key],
115
+ response: command_response
116
+ )
117
+
118
+ begin
119
+ yield command
120
+ rescue => e # rubocop:disable Style/RescueStandardError
121
+ error_response = {
122
+ success: false,
123
+ errors: [{message: command_error_message(e), type: :runtime_error}]
124
+ }
125
+
126
+ Rails.logger.error e.message
127
+ Rails.logger.error Rails.backtrace_cleaner.clean(e.backtrace).join("\n")
128
+
129
+ ApiMaker::Configuration.current.report_error(
130
+ command: command,
131
+ controller: controller,
132
+ error: e,
133
+ response: error_response
134
+ )
135
+
136
+ command.error(error_response)
137
+ end
138
+ end
139
+
140
+ def execute_service_or_fail(service_class, *args, &blk)
141
+ response = service_class.execute(*args, &blk)
142
+
143
+ if response.success?
144
+ succeed!(success: true)
145
+ else
146
+ fail_command_from_service_error_response(response)
147
+ end
148
+ end
149
+
150
+ def fail_command_from_service_error_response(response)
151
+ fail!(errors: serialize_service_errors(response.errors))
152
+ end
153
+
154
+ def failure_response(errors:)
155
+ fail!(
156
+ model: serialized_model(model),
157
+ success: false,
158
+ errors: errors
159
+ )
160
+ end
161
+
162
+ def failure_save_response(additional_attributes: [], model:, params:, simple_model_errors: false)
163
+ raise "Cannot receive additional attributes unless simple model errors" if !additional_attributes.empty? && !simple_model_errors
164
+
165
+ error_messages = if simple_model_errors
166
+ ApiMaker::SimpleModelErrors.execute!(additional_attributes: additional_attributes, model: model)
167
+ else
168
+ model.errors.full_messages
169
+ end
170
+
171
+ fail!(
172
+ error_type: :validation_error,
173
+ errors: error_messages.map { |error_message| {message: error_message, type: :validation_error} },
174
+ model: serialized_model(model),
175
+ success: false,
176
+ validation_errors: ApiMaker::ValidationErrorsGeneratorService.execute!(model: model, params: params)
177
+ )
178
+ end
179
+
180
+ def model_class
181
+ @model_class ||= collection.klass
182
+ end
183
+
184
+ def save_models_or_fail(*models, simple_model_errors: false)
185
+ response = ApiMaker::Models::Save.execute(models: models, simple_model_errors: simple_model_errors)
186
+
187
+ if response.success?
188
+ succeed!(success: true)
189
+ true
190
+ else
191
+ fail!(errors: response.error_messages.map { |error| {message: error, type: :validation_error} })
192
+ end
193
+ end
194
+
195
+ def serialize_service_errors(errors)
196
+ errors.map do |error|
197
+ {
198
+ message: error.message,
199
+ type: error.type
200
+ }
201
+ end
202
+ end
203
+
204
+ def fail!(*args, &blk)
205
+ if args.is_a?(Hash) && args.key?(:errors)
206
+ error_messages = args.fetch(:errors).map do |error|
207
+ if error.is_a?(Hash) && error.key?(:message)
208
+ error.fetch(:message)
209
+ else
210
+ error
211
+ end
212
+ end
213
+ else
214
+ error_messages = ["Command failed"]
215
+ end
216
+
217
+ error = ApiMaker::CommandFailedError.new(error_messages)
218
+ error.api_maker_args = args
219
+ error.api_maker_block = blk
220
+
221
+ raise error
222
+ end
223
+
224
+ def succeed!(*args, &blk)
225
+ command.result(*args, &blk)
226
+ end
227
+
228
+ def inspect
229
+ "#<#{self.class.name}:#{__id__}>"
230
+ end
231
+
232
+ private
233
+
234
+ def serialized_model(model)
235
+ collection_serializer = ApiMaker::CollectionSerializer.new(
236
+ ability: current_ability,
237
+ api_maker_args: api_maker_args,
238
+ collection: [model],
239
+ model_class: model.class,
240
+ query_params: args&.dig(:query_params)
241
+ )
242
+ collection_serializer.result
243
+ end
244
+
245
+ def serialized_resource(model)
246
+ ApiMaker::Serializer.new(ability: current_ability, api_maker_args: api_maker_args, model: model)
247
+ end
248
+ end