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,43 @@
1
+ class ApiMaker::UpdateCommand < ApiMaker::BaseCommand
2
+ attr_reader :command, :model, :params, :serializer
3
+
4
+ def execute!
5
+ each_command do |command|
6
+ @command = command
7
+ @model = command.model
8
+ @params = command.args || {}
9
+ @serializer = serialized_resource(model)
10
+
11
+ if command.model.update(sanitize_parameters)
12
+ success_response
13
+ else
14
+ failure_response
15
+ end
16
+ end
17
+
18
+ ServicePattern::Response.new(success: true)
19
+ end
20
+
21
+ def failure_response
22
+ command.fail(
23
+ model: serializer.result,
24
+ success: false,
25
+ errors: model.errors.full_messages
26
+ )
27
+ end
28
+
29
+ def sanitize_parameters
30
+ serializer.resource_instance.permitted_params(ApiMaker::PermittedParamsArgument.new(command: command, model: model))
31
+ end
32
+
33
+ def serialized_resource(model)
34
+ ApiMaker::Serializer.new(ability: current_ability, args: api_maker_args, model: model)
35
+ end
36
+
37
+ def success_response
38
+ command.result(
39
+ model: serializer.result,
40
+ success: true
41
+ )
42
+ end
43
+ end
@@ -0,0 +1,21 @@
1
+ class ApiMaker::UpdateCommandService < ApiMaker::CommandService
2
+ def execute
3
+ ApiMaker::UpdateCommand.execute_in_thread!(
4
+ ability: ability,
5
+ args: args,
6
+ collection: collection,
7
+ commands: commands,
8
+ command_response: command_response,
9
+ controller: controller
10
+ )
11
+ ServicePattern::Response.new(success: true)
12
+ end
13
+
14
+ def collection
15
+ @collection ||= model_class.accessible_by(@ability, :update).where(model_class.primary_key => ids)
16
+ end
17
+
18
+ def ids
19
+ @commands.values.map { |command| command.fetch("primary_key") }
20
+ end
21
+ end
@@ -0,0 +1,35 @@
1
+ class ApiMaker::ValidCommand < ApiMaker::BaseCommand
2
+ attr_reader :command, :model, :params, :serializer
3
+
4
+ def execute!
5
+ each_command do |command|
6
+ @command = command
7
+ @params = command.args || {}
8
+
9
+ if command.model_id.present?
10
+ model = resource_instance_class.find(command.model_id)
11
+ else
12
+ model = resource_instance_class.new
13
+ end
14
+
15
+ serializer = serialized_resource(model)
16
+ model.assign_attributes(sanitize_parameters(serializer))
17
+
18
+ command.result(valid: model.valid?, errors: model.errors.full_messages)
19
+ end
20
+
21
+ ServicePattern::Response.new(success: true)
22
+ end
23
+
24
+ def resource_instance_class
25
+ collection.klass
26
+ end
27
+
28
+ def sanitize_parameters(serializer)
29
+ serializer.resource_instance.permitted_params(ApiMaker::PermittedParamsArgument.new(command: command, model: serializer.model))
30
+ end
31
+
32
+ def serialized_resource(model)
33
+ ApiMaker::Serializer.new(ability: current_ability, args: api_maker_args, model: model)
34
+ end
35
+ end
@@ -0,0 +1,21 @@
1
+ class ApiMaker::ValidCommandService < ApiMaker::CommandService
2
+ def execute
3
+ ApiMaker::ValidCommand.execute_in_thread!(
4
+ ability: ability,
5
+ args: args,
6
+ collection: collection,
7
+ commands: commands,
8
+ command_response: command_response,
9
+ controller: controller
10
+ )
11
+ ServicePattern::Response.new(success: true)
12
+ end
13
+
14
+ def collection
15
+ @collection ||= model_class.accessible_by(@ability, :valid).where(model_class.primary_key => ids)
16
+ end
17
+
18
+ def ids
19
+ @commands.values.map { |command| command.fetch("primary_key") }
20
+ end
21
+ end
@@ -0,0 +1,15 @@
1
+ <div class="api-maker-data"<%
2
+
3
+ Devise.mappings.each do |scope|
4
+ model = __send__("current_#{scope[0]}")
5
+ next unless model
6
+
7
+ resource_class = ApiMaker::Serializer.resource_for(model.class)
8
+ next unless resource_class
9
+
10
+ serializer = ApiMaker::Serializer.new(ability: current_ability, args: api_maker_args, model: model) if model
11
+
12
+ %> data-current-<%= scope[0].to_s.dasherize %>="<%= model ? serializer.to_json : null %>"<%
13
+ end
14
+
15
+ %>></div>
@@ -0,0 +1,55 @@
1
+ AddModelVirtualAttributeCheck: { }
2
+ AlwaysAddDbIndexCheck: { }
3
+ #CheckSaveReturnValueCheck: { }
4
+ #CheckDestroyReturnValueCheck: { }
5
+ DefaultScopeIsEvilCheck: { }
6
+ DryBundlerInCapistranoCheck: { }
7
+ #HashSyntaxCheck: { }
8
+ IsolateSeedDataCheck: { }
9
+ KeepFindersOnTheirOwnModelCheck: { }
10
+ LawOfDemeterCheck: { }
11
+ #LongLineCheck: { max_line_length: 80 }
12
+ MoveCodeIntoControllerCheck: { }
13
+ MoveCodeIntoHelperCheck: { array_count: 3 }
14
+ MoveCodeIntoModelCheck: { use_count: 2 }
15
+ MoveFinderToNamedScopeCheck: { }
16
+ #MoveModelLogicIntoModelCheck: { use_count: 4 }
17
+ NeedlessDeepNestingCheck: { nested_count: 2 }
18
+ NotRescueExceptionCheck: { }
19
+ NotUseDefaultRouteCheck: { }
20
+ NotUseTimeAgoInWordsCheck: { }
21
+ OveruseRouteCustomizationsCheck: { customize_count: 3 }
22
+ ProtectMassAssignmentCheck: { }
23
+ RemoveEmptyHelpersCheck: { }
24
+ #RemoveTabCheck: { }
25
+ RemoveTrailingWhitespaceCheck: { }
26
+ RemoveUnusedMethodsInControllersCheck: {
27
+ except_methods: [
28
+ "ApiMaker::BaseController#current_ability",
29
+ "ApiMaker::CommandsController#create",
30
+ "ApiMaker::DeviseController#do_sign_in",
31
+ "ApiMaker::DeviseController#do_sign_out",
32
+ "ApiMaker::MemberActionsController#create",
33
+ "ApiMaker::ModelController#index",
34
+ "ApiMaker::ModelController#show",
35
+ "ApiMaker::ModelController#create",
36
+ "ApiMaker::ModelController#edit",
37
+ "ApiMaker::ModelController#validate"
38
+ ]
39
+ }
40
+ RemoveUnusedMethodsInHelpersCheck: { except_methods: [] }
41
+ RemoveUnusedMethodsInModelsCheck: { except_methods: [] }
42
+ ReplaceComplexCreationWithFactoryMethodCheck: { attribute_assignment_count: 2 }
43
+ ReplaceInstanceVariableWithLocalVariableCheck: { }
44
+ RestrictAutoGeneratedRoutesCheck: { }
45
+ SimplifyRenderInControllersCheck: { }
46
+ SimplifyRenderInViewsCheck: { }
47
+ #UseBeforeFilterCheck: { customize_count: 2 }
48
+ UseModelAssociationCheck: { }
49
+ UseMultipartAlternativeAsContentTypeOfEmailCheck: { }
50
+ #UseParenthesesInMethodDefCheck: { }
51
+ UseObserverCheck: { }
52
+ UseQueryAttributeCheck: { }
53
+ UseSayWithTimeInMigrationsCheck: { }
54
+ UseScopeAccessCheck: { }
55
+ UseTurboSprocketsRails3Check: { }
@@ -0,0 +1,7 @@
1
+ ApiMaker::Engine.routes.draw do
2
+ post "commands" => "commands#create"
3
+ post "devise/do_sign_in" => "devise#do_sign_in"
4
+ post "devise/do_sign_out" => "devise#do_sign_out"
5
+
6
+ resources :session_statuses, only: :create
7
+ end
@@ -0,0 +1,36 @@
1
+ require "api_maker/engine"
2
+
3
+ require "active_record_query_fixer"
4
+ require "ransack"
5
+ require "service_pattern"
6
+
7
+ require_relative "api_maker/action_controller_base_extensions"
8
+
9
+ module ApiMaker
10
+ dir = "#{__dir__}/api_maker"
11
+
12
+ autoload :Ability, "#{dir}/ability"
13
+ autoload :AbilityLoader, "#{dir}/ability_loader"
14
+ autoload :BaseCommand, "#{dir}/base_command"
15
+ autoload :BaseResource, "#{dir}/base_resource"
16
+ autoload :CollectionSerializer, "#{dir}/collection_serializer"
17
+ autoload :CommandSpecHelper, "#{dir}/command_spec_helper"
18
+ autoload :Configuration, "#{dir}/configuration"
19
+ autoload :IndividualCommand, "#{dir}/individual_command"
20
+ autoload :Loader, "#{dir}/loader"
21
+ autoload :MemoryStorage, "#{dir}/memory_storage"
22
+ autoload :ModelExtensions, "#{dir}/model_extensions"
23
+ autoload :PermittedParamsArgument, "#{dir}/permitted_params_argument"
24
+ autoload :Preloader, "#{dir}/preloader"
25
+ autoload :PreloaderBelongsTo, "#{dir}/preloader_belongs_to"
26
+ autoload :PreloaderHasMany, "#{dir}/preloader_has_many"
27
+ autoload :PreloaderHasOne, "#{dir}/preloader_has_one"
28
+ autoload :PreloaderThrough, "#{dir}/preloader_through"
29
+ autoload :RelationshipIncluder, "#{dir}/relationship_includer"
30
+ autoload :ResourceRouting, "#{dir}/resource_routing"
31
+ autoload :ResultParser, "#{dir}/result_parser"
32
+ autoload :Serializer, "#{dir}/serializer"
33
+ autoload :SpecHelper, "#{dir}/spec_helper"
34
+ end
35
+
36
+ require_relative "api_maker/railtie"
@@ -0,0 +1,39 @@
1
+ class ApiMaker::Ability
2
+ include CanCan::Ability
3
+
4
+ attr_reader :loader
5
+
6
+ def initialize(args: nil)
7
+ @args = args
8
+ @loader = ApiMaker::AbilityLoader.new(ability: self, args: args)
9
+ end
10
+
11
+ # Override methods from CanCan::Ability to first load abilities from the given resource
12
+ def can?(*args)
13
+ subject = args.second
14
+ load_abilities(subject)
15
+ super
16
+ end
17
+
18
+ def model_adapter(*args)
19
+ subject = args.first
20
+ load_abilities(subject)
21
+ super
22
+ end
23
+
24
+ def load_abilities(subject)
25
+ return unless active_record?(subject)
26
+
27
+ if subject.class == Class
28
+ loader.load_model_class(subject)
29
+ elsif subject.class != Class
30
+ loader.load_model_class(subject.class)
31
+ end
32
+ end
33
+
34
+ def active_record?(subject)
35
+ return subject < ActiveRecord::Base if subject.class == Class
36
+
37
+ subject.is_a?(ActiveRecord::Base)
38
+ end
39
+ end
@@ -0,0 +1,21 @@
1
+ class ApiMaker::AbilityLoader
2
+ def initialize(ability:, args:)
3
+ @ability = ability
4
+ @args = args
5
+ @loaded_model_names = {}
6
+ end
7
+
8
+ def load_model_class(model_class)
9
+ return if @loaded_model_names.key?(model_class.name)
10
+
11
+ resource = ApiMaker::MemoryStorage.current.resource_for_model(model_class)
12
+ load_resource(resource)
13
+ end
14
+
15
+ def load_resource(resource)
16
+ return if @loaded_model_names.key?(resource.model_class_name)
17
+
18
+ resource.new(ability: @ability, args: @args, model: nil).abilities
19
+ @loaded_model_names[resource.model_class_name] = true
20
+ end
21
+ end
@@ -0,0 +1,5 @@
1
+ ActionController::Base.__send__(:define_method, :api_maker_args) do
2
+ @api_maker_args ||= {}
3
+ end
4
+
5
+ ActionController::Base.helper_method(:api_maker_args)
@@ -0,0 +1,81 @@
1
+ class ApiMaker::BaseCommand
2
+ attr_reader :api_maker_args, :commands, :command_response, :collection, :controller, :current_ability
3
+
4
+ # Returns true if the gem "goldiloader" is present in the app
5
+ def self.goldiloader?
6
+ @goldiloader = Gem::Specification.find_all_by_name("goldiloader").any? if @goldiloader.nil?
7
+ @goldiloader
8
+ end
9
+
10
+ def initialize(ability:, args:, collection:, commands:, command_response:, controller:)
11
+ @api_maker_args = args
12
+ @current_ability = ability
13
+ @collection = collection
14
+ @commands = commands
15
+ @command_response = command_response
16
+ @controller = controller
17
+
18
+ # Make it possible to do custom preloads (useful in threadded mode that doesnt support Goldiloader)
19
+ @collection = custom_collection(@collection) if respond_to?(:custom_collection)
20
+ end
21
+
22
+ def self.execute_in_thread!(**args)
23
+ args.fetch(:command_response).with_thread do
24
+ new(**args).execute!
25
+ end
26
+ end
27
+
28
+ def each_command(args = {}, &blk)
29
+ if args[:threadded]
30
+ # Goldiloader doesn't work with threads (loads all relationships for each thread)
31
+ @collection = @collection.auto_include(false) if ApiMaker::BaseCommand.goldiloader?
32
+
33
+ # Load relationship before commands so each command doesn't query on its own
34
+ @collection.load
35
+ end
36
+
37
+ @commands.each do |command_id, command_data|
38
+ if args[:threadded]
39
+ command_response.with_thread do
40
+ run_command(command_id, command_data, &blk)
41
+ end
42
+ else
43
+ run_command(command_id, command_data, &blk)
44
+ end
45
+ end
46
+ end
47
+
48
+ delegate :result_for_command, to: :command_response
49
+
50
+ private
51
+
52
+ def run_command(command_id, command_data)
53
+ command = ApiMaker::IndividualCommand.new(
54
+ args: command_data[:args],
55
+ collection: @collection,
56
+ command: self,
57
+ id: command_id,
58
+ primary_key: command_data[:primary_key],
59
+ response: command_response
60
+ )
61
+
62
+ begin
63
+ yield command
64
+ rescue => e # rubocop:disable Style/RescueStandardError
65
+ command.error(success: false, errors: [command_error_message(e)])
66
+
67
+ Rails.logger.error e.message
68
+ Rails.logger.error e.backtrace.join("\n")
69
+
70
+ ApiMaker::Configuration.current.report_error(controller: controller, error: e)
71
+ end
72
+ end
73
+
74
+ def command_error_message(error)
75
+ if Rails.application.config.consider_all_requests_local
76
+ "#{error.class.name}: #{error.message}"
77
+ else
78
+ "Internal server error"
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,78 @@
1
+ class ApiMaker::BaseResource
2
+ attr_reader :ability, :args, :model
3
+
4
+ delegate :can, to: :ability
5
+
6
+ CRUD = [:create, :read, :update, :destroy].freeze
7
+
8
+ def self.attributes(*attributes, **args)
9
+ attributes.each do |attribute|
10
+ ApiMaker::MemoryStorage.current.add(self, :attributes, attribute, args)
11
+ end
12
+ end
13
+
14
+ def self._attributes
15
+ ApiMaker::MemoryStorage.current.storage_for(self, :attributes)
16
+ end
17
+
18
+ def self.collection_commands(*list)
19
+ list.each do |collection_command|
20
+ ApiMaker::MemoryStorage.current.add(self, :collection_commands, collection_command)
21
+ end
22
+ end
23
+
24
+ def self.member_commands(*list)
25
+ list.each do |member_command|
26
+ ApiMaker::MemoryStorage.current.add(self, :member_commands, member_command)
27
+ end
28
+ end
29
+
30
+ def self.model_class=(klass)
31
+ # Set the name to avoid reloading issues with Rails
32
+ @model_class_name ||= klass.name
33
+ ApiMaker::MemoryStorage.current.model_class_for(resource: self, klass: klass)
34
+ end
35
+
36
+ def self.model_class
37
+ # Use the name to constantize to avoid reloading issues with Rails
38
+ model_class_name.constantize
39
+ end
40
+
41
+ def self.model_class_name
42
+ @model_class_name ||= name.gsub(/Resource$/, "").gsub(/^Resources::/, "")
43
+ end
44
+
45
+ def self.relationships(*relationships)
46
+ relationships.each do |relationship|
47
+ ApiMaker::MemoryStorage.current.add(self, :relationships, relationship)
48
+ end
49
+ end
50
+
51
+ def self._relationships
52
+ ApiMaker::MemoryStorage.current.storage_for(self, :relationships)
53
+ end
54
+
55
+ def self.collection_name
56
+ @collection_name ||= plural_name.underscore.dasherize
57
+ end
58
+
59
+ def self.default_select
60
+ _attributes.select do |_attribute_name, args|
61
+ !args.fetch(:args).key?(:selected_by_default) || args.fetch(:args).fetch(:selected_by_default)
62
+ end
63
+ end
64
+
65
+ def self.plural_name
66
+ @plural_name ||= short_name.pluralize
67
+ end
68
+
69
+ def self.short_name
70
+ @short_name ||= name.match(/\AResources::(.+)Resource\Z/)[1]
71
+ end
72
+
73
+ def initialize(ability: nil, args: {}, model:)
74
+ @ability = ability
75
+ @args = args
76
+ @model = model
77
+ end
78
+ end