iron_admin 0.4.1 → 0.6.0

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 (456) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +66 -30
  3. data/app/assets/config/iron_admin_manifest.js +12 -0
  4. data/app/components/iron_admin/dashboards/metric_card_component.html.haml +8 -3
  5. data/app/components/iron_admin/dashboards/metric_card_component.rb +21 -1
  6. data/app/components/iron_admin/form/belongs_to_component.rb +60 -6
  7. data/app/components/iron_admin/form/nested_form_component.html.haml +28 -0
  8. data/app/components/iron_admin/form/nested_form_component.rb +81 -0
  9. data/app/components/iron_admin/resources/data_table_component.html.haml +1 -1
  10. data/app/components/iron_admin/resources/data_table_component.rb +6 -0
  11. data/app/components/iron_admin/resources/show_field_component.rb +2 -0
  12. data/app/controllers/iron_admin/application_controller.rb +32 -1
  13. data/app/controllers/iron_admin/concerns/action_executable.rb +44 -0
  14. data/app/controllers/iron_admin/concerns/filterable.rb +84 -0
  15. data/app/controllers/iron_admin/concerns/json_params_coercion.rb +67 -0
  16. data/app/controllers/iron_admin/concerns/nested_permittable.rb +36 -0
  17. data/app/controllers/iron_admin/concerns/scopeable.rb +84 -0
  18. data/app/controllers/iron_admin/concerns/searchable.rb +13 -16
  19. data/app/controllers/iron_admin/exports_controller.rb +15 -11
  20. data/app/controllers/iron_admin/imports_controller.rb +83 -0
  21. data/app/controllers/iron_admin/live_controller.rb +12 -0
  22. data/app/controllers/iron_admin/resources_controller.rb +118 -117
  23. data/app/controllers/iron_admin/search_controller.rb +5 -5
  24. data/app/controllers/iron_admin/tools_controller.rb +63 -4
  25. data/app/helpers/iron_admin/application_helper.rb +33 -14
  26. data/app/helpers/iron_admin/field_display_helper.rb +160 -1
  27. data/app/javascript/iron_admin/controllers/cp_nested_form_controller.js +113 -0
  28. data/app/javascript/iron_admin/controllers/filter_operator_controller.js +10 -0
  29. data/app/javascript/iron_admin/index.js +4 -0
  30. data/app/views/iron_admin/dashboard/index.html.haml +7 -2
  31. data/app/views/iron_admin/form/_nested_row.html.haml +70 -0
  32. data/app/views/iron_admin/imports/new.html.haml +27 -0
  33. data/app/views/iron_admin/imports/preview.html.haml +31 -0
  34. data/app/views/iron_admin/resources/_form.html.haml +81 -5
  35. data/app/views/iron_admin/resources/action_form.html.haml +62 -0
  36. data/app/views/iron_admin/resources/bulk_action_form.html.haml +62 -0
  37. data/app/views/iron_admin/resources/index.html.haml +60 -18
  38. data/app/views/iron_admin/tools/action_form.html.haml +57 -0
  39. data/app/views/iron_admin/tools/show.html.haml +20 -1
  40. data/config/importmap.rb +2 -0
  41. data/config/locales/en.yml +36 -0
  42. data/config/routes.rb +7 -0
  43. data/docs/components/filter-components.md +60 -0
  44. data/docs/getting-started/installation.md +113 -27
  45. data/docs/getting-started/quick-start.md +17 -1
  46. data/docs/guides/authentication.md +7 -6
  47. data/docs/guides/custom-adapters.md +628 -0
  48. data/docs/guides/extending.md +132 -3
  49. data/docs/guides/fields.md +1 -1
  50. data/docs/guides/resources.md +78 -2
  51. data/docs/guides/troubleshooting.md +68 -10
  52. data/docs/reference/routes.md +10 -0
  53. data/lib/generators/iron_admin/install/install_generator.rb +49 -4
  54. data/lib/generators/iron_admin/install/templates/dashboard.rb.tt +5 -1
  55. data/lib/generators/iron_admin/install_audit/install_audit_generator.rb +26 -10
  56. data/lib/generators/iron_admin/resource/resource_generator.rb +1 -1
  57. data/lib/generators/iron_admin/resource/templates/resource.rb.tt +6 -2
  58. data/lib/iron_admin/action_field.rb +56 -0
  59. data/lib/iron_admin/adapters/active_record.rb +237 -0
  60. data/lib/iron_admin/adapters/base.rb +340 -0
  61. data/lib/iron_admin/adapters/http/column_descriptor.rb +10 -0
  62. data/lib/iron_admin/adapters/http/configuration.rb +27 -0
  63. data/lib/iron_admin/adapters/http/connection.rb +117 -0
  64. data/lib/iron_admin/adapters/http/model_proxy.rb +75 -0
  65. data/lib/iron_admin/adapters/http/query.rb +111 -0
  66. data/lib/iron_admin/adapters/http/record.rb +83 -0
  67. data/lib/iron_admin/adapters/http/type_inferrer.rb +51 -0
  68. data/lib/iron_admin/adapters/http.rb +241 -0
  69. data/lib/iron_admin/adapters/mongoid/association_wrapper.rb +74 -0
  70. data/lib/iron_admin/adapters/mongoid/column_descriptor.rb +10 -0
  71. data/lib/iron_admin/adapters/mongoid.rb +284 -0
  72. data/lib/iron_admin/adapters/registry.rb +44 -0
  73. data/lib/iron_admin/concerns/importable.rb +81 -0
  74. data/lib/iron_admin/concerns/live_updatable.rb +38 -0
  75. data/lib/iron_admin/concerns/nestable.rb +69 -0
  76. data/lib/iron_admin/concerns/soft_deletable.rb +61 -0
  77. data/lib/iron_admin/configuration.rb +20 -0
  78. data/lib/iron_admin/dashboard.rb +15 -20
  79. data/lib/iron_admin/engine.rb +35 -22
  80. data/lib/iron_admin/errors.rb +29 -0
  81. data/lib/iron_admin/field_inferrer.rb +61 -22
  82. data/lib/iron_admin/filters/active_record_query_builder.rb +63 -0
  83. data/lib/iron_admin/filters/base_query_builder.rb +55 -0
  84. data/lib/iron_admin/filters/http_query_builder.rb +39 -0
  85. data/lib/iron_admin/filters/mongoid_query_builder.rb +58 -0
  86. data/lib/iron_admin/filters/query_builder.rb +12 -0
  87. data/lib/iron_admin/import/column_mapper.rb +42 -0
  88. data/lib/iron_admin/import/import_preview.rb +10 -0
  89. data/lib/iron_admin/import/import_result.rb +10 -0
  90. data/lib/iron_admin/import/importer.rb +231 -0
  91. data/lib/iron_admin/import/parser/csv.rb +34 -0
  92. data/lib/iron_admin/import/parser/json.rb +36 -0
  93. data/lib/iron_admin/import/type_caster.rb +47 -0
  94. data/lib/iron_admin/live/broadcaster.rb +43 -0
  95. data/lib/iron_admin/live/poll_cache.rb +28 -0
  96. data/lib/iron_admin/live.rb +29 -0
  97. data/lib/iron_admin/nested_association.rb +15 -0
  98. data/lib/iron_admin/nested_attributes_validator.rb +25 -0
  99. data/lib/iron_admin/policy.rb +72 -13
  100. data/lib/iron_admin/resource.rb +149 -91
  101. data/lib/iron_admin/resource_registry.rb +77 -4
  102. data/lib/iron_admin/tool.rb +50 -0
  103. data/lib/iron_admin/tool_action.rb +73 -0
  104. data/lib/iron_admin/tool_context.rb +49 -0
  105. data/lib/iron_admin/version.rb +1 -1
  106. data/lib/iron_admin.rb +46 -10
  107. data/vendor/bundle/ruby/3.2.0/cache/addressable-2.9.0.gem +0 -0
  108. data/vendor/bundle/ruby/3.2.0/cache/crack-1.0.1.gem +0 -0
  109. data/vendor/bundle/ruby/3.2.0/cache/faraday-2.14.1.gem +0 -0
  110. data/vendor/bundle/ruby/3.2.0/cache/faraday-net_http-3.4.2.gem +0 -0
  111. data/vendor/bundle/ruby/3.2.0/cache/hashdiff-1.2.1.gem +0 -0
  112. data/vendor/bundle/ruby/3.2.0/cache/net-http-0.9.1.gem +0 -0
  113. data/vendor/bundle/ruby/3.2.0/cache/public_suffix-7.0.5.gem +0 -0
  114. data/vendor/bundle/ruby/3.2.0/cache/rexml-3.4.4.gem +0 -0
  115. data/vendor/bundle/ruby/3.2.0/cache/webmock-3.26.2.gem +0 -0
  116. data/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux/3.2.0/bigdecimal-4.0.1/bigdecimal.so +0 -0
  117. data/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux/3.2.0/bigdecimal-4.0.1/gem_make.out +6 -6
  118. data/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux/3.2.0/bigdecimal-4.0.1/mkmf.log +25 -25
  119. data/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux/3.2.0/date-3.5.1/date_core.so +0 -0
  120. data/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux/3.2.0/date-3.5.1/gem_make.out +6 -6
  121. data/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux/3.2.0/date-3.5.1/mkmf.log +4 -4
  122. data/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux/3.2.0/erb-6.0.1/erb/escape.so +0 -0
  123. data/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux/3.2.0/erb-6.0.1/gem_make.out +6 -6
  124. data/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux/3.2.0/erb-6.0.1/mkmf.log +2 -2
  125. data/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux/3.2.0/io-console-0.8.2/gem_make.out +6 -6
  126. data/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux/3.2.0/io-console-0.8.2/io/console.so +0 -0
  127. data/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux/3.2.0/io-console-0.8.2/mkmf.log +22 -22
  128. data/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux/3.2.0/json-2.18.1/gem_make.out +6 -6
  129. data/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux/3.2.0/json-2.18.1/json/ext/generator.so +0 -0
  130. data/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux/3.2.0/json-2.18.1/json/ext/parser.so +0 -0
  131. data/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux/3.2.0/json-2.18.1/mkmf.log +9 -9
  132. data/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux/3.2.0/nio4r-2.7.5/gem_make.out +6 -6
  133. data/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux/3.2.0/nio4r-2.7.5/mkmf.log +12 -12
  134. data/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux/3.2.0/nio4r-2.7.5/nio4r_ext.so +0 -0
  135. data/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux/3.2.0/prism-1.9.0/gem_make.out +6 -6
  136. data/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux/3.2.0/prism-1.9.0/mkmf.log +6 -6
  137. data/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux/3.2.0/prism-1.9.0/prism/prism.so +0 -0
  138. data/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux/3.2.0/psych-5.3.1/gem_make.out +6 -6
  139. data/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux/3.2.0/psych-5.3.1/mkmf.log +7 -7
  140. data/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux/3.2.0/psych-5.3.1/psych.so +0 -0
  141. data/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux/3.2.0/racc-1.8.1/gem_make.out +6 -6
  142. data/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux/3.2.0/racc-1.8.1/racc/cparse.so +0 -0
  143. data/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux/3.2.0/redcarpet-3.6.1/gem_make.out +6 -6
  144. data/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux/3.2.0/redcarpet-3.6.1/redcarpet.so +0 -0
  145. data/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux/3.2.0/stringio-3.2.0/gem_make.out +6 -6
  146. data/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux/3.2.0/stringio-3.2.0/mkmf.log +2 -2
  147. data/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux/3.2.0/stringio-3.2.0/stringio.so +0 -0
  148. data/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux/3.2.0/websocket-driver-0.8.0/gem_make.out +6 -6
  149. data/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux/3.2.0/websocket-driver-0.8.0/websocket_mask.so +0 -0
  150. data/vendor/bundle/ruby/3.2.0/gems/addressable-2.9.0/CHANGELOG.md +326 -0
  151. data/vendor/bundle/ruby/3.2.0/gems/addressable-2.9.0/LICENSE.txt +202 -0
  152. data/vendor/bundle/ruby/3.2.0/gems/addressable-2.9.0/README.md +121 -0
  153. data/vendor/bundle/ruby/3.2.0/gems/addressable-2.9.0/lib/addressable/idna/native.rb +66 -0
  154. data/vendor/bundle/ruby/3.2.0/gems/addressable-2.9.0/lib/addressable/idna/pure.rb +4710 -0
  155. data/vendor/bundle/ruby/3.2.0/gems/addressable-2.9.0/lib/addressable/idna.rb +26 -0
  156. data/vendor/bundle/ruby/3.2.0/gems/addressable-2.9.0/lib/addressable/template.rb +1040 -0
  157. data/vendor/bundle/ruby/3.2.0/gems/addressable-2.9.0/lib/addressable/uri.rb +2602 -0
  158. data/vendor/bundle/ruby/3.2.0/gems/addressable-2.9.0/lib/addressable/version.rb +31 -0
  159. data/vendor/bundle/ruby/3.2.0/gems/addressable-2.9.0/lib/addressable.rb +4 -0
  160. data/vendor/bundle/ruby/3.2.0/gems/bigdecimal-4.0.1/ext/bigdecimal/Makefile +3 -3
  161. data/vendor/bundle/ruby/3.2.0/gems/bigdecimal-4.0.1/lib/bigdecimal.so +0 -0
  162. data/vendor/bundle/ruby/3.2.0/gems/crack-1.0.1/History +58 -0
  163. data/vendor/bundle/ruby/3.2.0/gems/crack-1.0.1/LICENSE +20 -0
  164. data/vendor/bundle/ruby/3.2.0/gems/crack-1.0.1/README.md +43 -0
  165. data/vendor/bundle/ruby/3.2.0/gems/crack-1.0.1/lib/crack/json.rb +113 -0
  166. data/vendor/bundle/ruby/3.2.0/gems/crack-1.0.1/lib/crack/util.rb +17 -0
  167. data/vendor/bundle/ruby/3.2.0/gems/crack-1.0.1/lib/crack/version.rb +3 -0
  168. data/vendor/bundle/ruby/3.2.0/gems/crack-1.0.1/lib/crack/xml.rb +240 -0
  169. data/vendor/bundle/ruby/3.2.0/gems/crack-1.0.1/lib/crack.rb +8 -0
  170. data/vendor/bundle/ruby/3.2.0/gems/date-3.5.1/ext/date/Makefile +3 -3
  171. data/vendor/bundle/ruby/3.2.0/gems/date-3.5.1/lib/date_core.so +0 -0
  172. data/vendor/bundle/ruby/3.2.0/gems/erb-6.0.1/ext/erb/escape/Makefile +3 -3
  173. data/vendor/bundle/ruby/3.2.0/gems/erb-6.0.1/lib/erb/escape.so +0 -0
  174. data/vendor/bundle/ruby/3.2.0/gems/faraday-2.14.1/CHANGELOG.md +574 -0
  175. data/vendor/bundle/ruby/3.2.0/gems/faraday-2.14.1/LICENSE.md +20 -0
  176. data/vendor/bundle/ruby/3.2.0/gems/faraday-2.14.1/README.md +67 -0
  177. data/vendor/bundle/ruby/3.2.0/gems/faraday-2.14.1/Rakefile +12 -0
  178. data/vendor/bundle/ruby/3.2.0/gems/faraday-2.14.1/examples/client_spec.rb +119 -0
  179. data/vendor/bundle/ruby/3.2.0/gems/faraday-2.14.1/examples/client_test.rb +144 -0
  180. data/vendor/bundle/ruby/3.2.0/gems/faraday-2.14.1/lib/faraday/adapter/test.rb +311 -0
  181. data/vendor/bundle/ruby/3.2.0/gems/faraday-2.14.1/lib/faraday/adapter.rb +101 -0
  182. data/vendor/bundle/ruby/3.2.0/gems/faraday-2.14.1/lib/faraday/adapter_registry.rb +30 -0
  183. data/vendor/bundle/ruby/3.2.0/gems/faraday-2.14.1/lib/faraday/connection.rb +565 -0
  184. data/vendor/bundle/ruby/3.2.0/gems/faraday-2.14.1/lib/faraday/encoders/flat_params_encoder.rb +105 -0
  185. data/vendor/bundle/ruby/3.2.0/gems/faraday-2.14.1/lib/faraday/encoders/nested_params_encoder.rb +183 -0
  186. data/vendor/bundle/ruby/3.2.0/gems/faraday-2.14.1/lib/faraday/error.rb +202 -0
  187. data/vendor/bundle/ruby/3.2.0/gems/faraday-2.14.1/lib/faraday/logging/formatter.rb +118 -0
  188. data/vendor/bundle/ruby/3.2.0/gems/faraday-2.14.1/lib/faraday/methods.rb +6 -0
  189. data/vendor/bundle/ruby/3.2.0/gems/faraday-2.14.1/lib/faraday/middleware.rb +72 -0
  190. data/vendor/bundle/ruby/3.2.0/gems/faraday-2.14.1/lib/faraday/middleware_registry.rb +83 -0
  191. data/vendor/bundle/ruby/3.2.0/gems/faraday-2.14.1/lib/faraday/options/connection_options.rb +23 -0
  192. data/vendor/bundle/ruby/3.2.0/gems/faraday-2.14.1/lib/faraday/options/env.rb +204 -0
  193. data/vendor/bundle/ruby/3.2.0/gems/faraday-2.14.1/lib/faraday/options/proxy_options.rb +38 -0
  194. data/vendor/bundle/ruby/3.2.0/gems/faraday-2.14.1/lib/faraday/options/request_options.rb +23 -0
  195. data/vendor/bundle/ruby/3.2.0/gems/faraday-2.14.1/lib/faraday/options/ssl_options.rb +76 -0
  196. data/vendor/bundle/ruby/3.2.0/gems/faraday-2.14.1/lib/faraday/options.rb +219 -0
  197. data/vendor/bundle/ruby/3.2.0/gems/faraday-2.14.1/lib/faraday/parameters.rb +5 -0
  198. data/vendor/bundle/ruby/3.2.0/gems/faraday-2.14.1/lib/faraday/rack_builder.rb +248 -0
  199. data/vendor/bundle/ruby/3.2.0/gems/faraday-2.14.1/lib/faraday/request/authorization.rb +54 -0
  200. data/vendor/bundle/ruby/3.2.0/gems/faraday-2.14.1/lib/faraday/request/instrumentation.rb +58 -0
  201. data/vendor/bundle/ruby/3.2.0/gems/faraday-2.14.1/lib/faraday/request/json.rb +70 -0
  202. data/vendor/bundle/ruby/3.2.0/gems/faraday-2.14.1/lib/faraday/request/url_encoded.rb +60 -0
  203. data/vendor/bundle/ruby/3.2.0/gems/faraday-2.14.1/lib/faraday/request.rb +139 -0
  204. data/vendor/bundle/ruby/3.2.0/gems/faraday-2.14.1/lib/faraday/response/json.rb +74 -0
  205. data/vendor/bundle/ruby/3.2.0/gems/faraday-2.14.1/lib/faraday/response/logger.rb +39 -0
  206. data/vendor/bundle/ruby/3.2.0/gems/faraday-2.14.1/lib/faraday/response/raise_error.rb +83 -0
  207. data/vendor/bundle/ruby/3.2.0/gems/faraday-2.14.1/lib/faraday/response.rb +95 -0
  208. data/vendor/bundle/ruby/3.2.0/gems/faraday-2.14.1/lib/faraday/utils/headers.rb +150 -0
  209. data/vendor/bundle/ruby/3.2.0/gems/faraday-2.14.1/lib/faraday/utils/params_hash.rb +61 -0
  210. data/vendor/bundle/ruby/3.2.0/gems/faraday-2.14.1/lib/faraday/utils.rb +121 -0
  211. data/vendor/bundle/ruby/3.2.0/gems/faraday-2.14.1/lib/faraday/version.rb +5 -0
  212. data/vendor/bundle/ruby/3.2.0/gems/faraday-2.14.1/lib/faraday.rb +158 -0
  213. data/vendor/bundle/ruby/3.2.0/gems/faraday-2.14.1/spec/external_adapters/faraday_specs_setup.rb +14 -0
  214. data/vendor/bundle/ruby/3.2.0/gems/faraday-2.14.1/spec/faraday/adapter/test_spec.rb +442 -0
  215. data/vendor/bundle/ruby/3.2.0/gems/faraday-2.14.1/spec/faraday/adapter_registry_spec.rb +28 -0
  216. data/vendor/bundle/ruby/3.2.0/gems/faraday-2.14.1/spec/faraday/adapter_spec.rb +55 -0
  217. data/vendor/bundle/ruby/3.2.0/gems/faraday-2.14.1/spec/faraday/connection_spec.rb +841 -0
  218. data/vendor/bundle/ruby/3.2.0/gems/faraday-2.14.1/spec/faraday/error_spec.rb +175 -0
  219. data/vendor/bundle/ruby/3.2.0/gems/faraday-2.14.1/spec/faraday/middleware_registry_spec.rb +31 -0
  220. data/vendor/bundle/ruby/3.2.0/gems/faraday-2.14.1/spec/faraday/middleware_spec.rb +213 -0
  221. data/vendor/bundle/ruby/3.2.0/gems/faraday-2.14.1/spec/faraday/options/env_spec.rb +76 -0
  222. data/vendor/bundle/ruby/3.2.0/gems/faraday-2.14.1/spec/faraday/options/options_spec.rb +297 -0
  223. data/vendor/bundle/ruby/3.2.0/gems/faraday-2.14.1/spec/faraday/options/proxy_options_spec.rb +79 -0
  224. data/vendor/bundle/ruby/3.2.0/gems/faraday-2.14.1/spec/faraday/options/request_options_spec.rb +19 -0
  225. data/vendor/bundle/ruby/3.2.0/gems/faraday-2.14.1/spec/faraday/params_encoders/flat_spec.rb +42 -0
  226. data/vendor/bundle/ruby/3.2.0/gems/faraday-2.14.1/spec/faraday/params_encoders/nested_spec.rb +151 -0
  227. data/vendor/bundle/ruby/3.2.0/gems/faraday-2.14.1/spec/faraday/rack_builder_spec.rb +317 -0
  228. data/vendor/bundle/ruby/3.2.0/gems/faraday-2.14.1/spec/faraday/request/authorization_spec.rb +118 -0
  229. data/vendor/bundle/ruby/3.2.0/gems/faraday-2.14.1/spec/faraday/request/instrumentation_spec.rb +74 -0
  230. data/vendor/bundle/ruby/3.2.0/gems/faraday-2.14.1/spec/faraday/request/json_spec.rb +199 -0
  231. data/vendor/bundle/ruby/3.2.0/gems/faraday-2.14.1/spec/faraday/request/url_encoded_spec.rb +93 -0
  232. data/vendor/bundle/ruby/3.2.0/gems/faraday-2.14.1/spec/faraday/request_spec.rb +110 -0
  233. data/vendor/bundle/ruby/3.2.0/gems/faraday-2.14.1/spec/faraday/response/json_spec.rb +206 -0
  234. data/vendor/bundle/ruby/3.2.0/gems/faraday-2.14.1/spec/faraday/response/logger_spec.rb +299 -0
  235. data/vendor/bundle/ruby/3.2.0/gems/faraday-2.14.1/spec/faraday/response/raise_error_spec.rb +286 -0
  236. data/vendor/bundle/ruby/3.2.0/gems/faraday-2.14.1/spec/faraday/response_spec.rb +84 -0
  237. data/vendor/bundle/ruby/3.2.0/gems/faraday-2.14.1/spec/faraday/utils/headers_spec.rb +109 -0
  238. data/vendor/bundle/ruby/3.2.0/gems/faraday-2.14.1/spec/faraday/utils_spec.rb +120 -0
  239. data/vendor/bundle/ruby/3.2.0/gems/faraday-2.14.1/spec/faraday_spec.rb +43 -0
  240. data/vendor/bundle/ruby/3.2.0/gems/faraday-2.14.1/spec/spec_helper.rb +133 -0
  241. data/vendor/bundle/ruby/3.2.0/gems/faraday-2.14.1/spec/support/disabling_stub.rb +14 -0
  242. data/vendor/bundle/ruby/3.2.0/gems/faraday-2.14.1/spec/support/fake_safe_buffer.rb +15 -0
  243. data/vendor/bundle/ruby/3.2.0/gems/faraday-2.14.1/spec/support/faraday_middleware_subclasses.rb +18 -0
  244. data/vendor/bundle/ruby/3.2.0/gems/faraday-2.14.1/spec/support/helper_methods.rb +96 -0
  245. data/vendor/bundle/ruby/3.2.0/gems/faraday-2.14.1/spec/support/shared_examples/adapter.rb +105 -0
  246. data/vendor/bundle/ruby/3.2.0/gems/faraday-2.14.1/spec/support/shared_examples/params_encoder.rb +18 -0
  247. data/vendor/bundle/ruby/3.2.0/gems/faraday-2.14.1/spec/support/shared_examples/request_method.rb +263 -0
  248. data/vendor/bundle/ruby/3.2.0/gems/faraday-2.14.1/spec/support/streaming_response_checker.rb +35 -0
  249. data/vendor/bundle/ruby/3.2.0/gems/faraday-net_http-3.4.2/LICENSE.md +21 -0
  250. data/vendor/bundle/ruby/3.2.0/gems/faraday-net_http-3.4.2/README.md +57 -0
  251. data/vendor/bundle/ruby/3.2.0/gems/faraday-net_http-3.4.2/lib/faraday/adapter/net_http.rb +206 -0
  252. data/vendor/bundle/ruby/3.2.0/gems/faraday-net_http-3.4.2/lib/faraday/net_http/version.rb +7 -0
  253. data/vendor/bundle/ruby/3.2.0/gems/faraday-net_http-3.4.2/lib/faraday/net_http.rb +10 -0
  254. data/vendor/bundle/ruby/3.2.0/gems/hashdiff-1.2.1/Gemfile +8 -0
  255. data/vendor/bundle/ruby/3.2.0/gems/hashdiff-1.2.1/LICENSE +19 -0
  256. data/vendor/bundle/ruby/3.2.0/gems/hashdiff-1.2.1/README.md +323 -0
  257. data/vendor/bundle/ruby/3.2.0/gems/hashdiff-1.2.1/Rakefile +18 -0
  258. data/vendor/bundle/ruby/3.2.0/gems/hashdiff-1.2.1/changelog.md +127 -0
  259. data/vendor/bundle/ruby/3.2.0/gems/hashdiff-1.2.1/hashdiff.gemspec +39 -0
  260. data/vendor/bundle/ruby/3.2.0/gems/hashdiff-1.2.1/lib/hashdiff/compare_hashes.rb +101 -0
  261. data/vendor/bundle/ruby/3.2.0/gems/hashdiff-1.2.1/lib/hashdiff/diff.rb +185 -0
  262. data/vendor/bundle/ruby/3.2.0/gems/hashdiff-1.2.1/lib/hashdiff/lcs.rb +66 -0
  263. data/vendor/bundle/ruby/3.2.0/gems/hashdiff-1.2.1/lib/hashdiff/lcs_compare_arrays.rb +32 -0
  264. data/vendor/bundle/ruby/3.2.0/gems/hashdiff-1.2.1/lib/hashdiff/linear_compare_array.rb +159 -0
  265. data/vendor/bundle/ruby/3.2.0/gems/hashdiff-1.2.1/lib/hashdiff/patch.rb +88 -0
  266. data/vendor/bundle/ruby/3.2.0/gems/hashdiff-1.2.1/lib/hashdiff/util.rb +155 -0
  267. data/vendor/bundle/ruby/3.2.0/gems/hashdiff-1.2.1/lib/hashdiff/version.rb +5 -0
  268. data/vendor/bundle/ruby/3.2.0/gems/hashdiff-1.2.1/lib/hashdiff.rb +10 -0
  269. data/vendor/bundle/ruby/3.2.0/gems/io-console-0.8.2/ext/io/console/Makefile +3 -3
  270. data/vendor/bundle/ruby/3.2.0/gems/io-console-0.8.2/lib/io/console.so +0 -0
  271. data/vendor/bundle/ruby/3.2.0/gems/json-2.18.1/ext/json/ext/generator/Makefile +3 -3
  272. data/vendor/bundle/ruby/3.2.0/gems/json-2.18.1/ext/json/ext/parser/Makefile +3 -3
  273. data/vendor/bundle/ruby/3.2.0/gems/json-2.18.1/lib/json/ext/generator.so +0 -0
  274. data/vendor/bundle/ruby/3.2.0/gems/json-2.18.1/lib/json/ext/parser.so +0 -0
  275. data/vendor/bundle/ruby/3.2.0/gems/net-http-0.9.1/BSDL +22 -0
  276. data/vendor/bundle/ruby/3.2.0/gems/net-http-0.9.1/COPYING +56 -0
  277. data/vendor/bundle/ruby/3.2.0/gems/net-http-0.9.1/README.md +93 -0
  278. data/vendor/bundle/ruby/3.2.0/gems/net-http-0.9.1/doc/net-http/examples.rdoc +31 -0
  279. data/vendor/bundle/ruby/3.2.0/gems/net-http-0.9.1/doc/net-http/included_getters.rdoc +3 -0
  280. data/vendor/bundle/ruby/3.2.0/gems/net-http-0.9.1/lib/net/http/exceptions.rb +35 -0
  281. data/vendor/bundle/ruby/3.2.0/gems/net-http-0.9.1/lib/net/http/generic_request.rb +429 -0
  282. data/vendor/bundle/ruby/3.2.0/gems/net-http-0.9.1/lib/net/http/header.rb +985 -0
  283. data/vendor/bundle/ruby/3.2.0/gems/net-http-0.9.1/lib/net/http/proxy_delta.rb +17 -0
  284. data/vendor/bundle/ruby/3.2.0/gems/net-http-0.9.1/lib/net/http/request.rb +88 -0
  285. data/vendor/bundle/ruby/3.2.0/gems/net-http-0.9.1/lib/net/http/requests.rb +444 -0
  286. data/vendor/bundle/ruby/3.2.0/gems/net-http-0.9.1/lib/net/http/response.rb +739 -0
  287. data/vendor/bundle/ruby/3.2.0/gems/net-http-0.9.1/lib/net/http/responses.rb +1242 -0
  288. data/vendor/bundle/ruby/3.2.0/gems/net-http-0.9.1/lib/net/http/status.rb +84 -0
  289. data/vendor/bundle/ruby/3.2.0/gems/net-http-0.9.1/lib/net/http.rb +2608 -0
  290. data/vendor/bundle/ruby/3.2.0/gems/net-http-0.9.1/lib/net/https.rb +23 -0
  291. data/vendor/bundle/ruby/3.2.0/gems/nio4r-2.7.5/ext/nio4r/Makefile +3 -3
  292. data/vendor/bundle/ruby/3.2.0/gems/nio4r-2.7.5/lib/nio4r_ext.so +0 -0
  293. data/vendor/bundle/ruby/3.2.0/gems/prism-1.9.0/ext/prism/Makefile +3 -3
  294. data/vendor/bundle/ruby/3.2.0/gems/prism-1.9.0/lib/prism/prism.so +0 -0
  295. data/vendor/bundle/ruby/3.2.0/gems/psych-5.3.1/ext/psych/Makefile +3 -3
  296. data/vendor/bundle/ruby/3.2.0/gems/psych-5.3.1/lib/psych.so +0 -0
  297. data/vendor/bundle/ruby/3.2.0/gems/public_suffix-7.0.5/CHANGELOG.md +649 -0
  298. data/vendor/bundle/ruby/3.2.0/gems/public_suffix-7.0.5/Gemfile +16 -0
  299. data/vendor/bundle/ruby/3.2.0/gems/public_suffix-7.0.5/LICENSE.txt +22 -0
  300. data/vendor/bundle/ruby/3.2.0/gems/public_suffix-7.0.5/README.md +231 -0
  301. data/vendor/bundle/ruby/3.2.0/gems/public_suffix-7.0.5/SECURITY.md +24 -0
  302. data/vendor/bundle/ruby/3.2.0/gems/public_suffix-7.0.5/data/list.txt +16298 -0
  303. data/vendor/bundle/ruby/3.2.0/gems/public_suffix-7.0.5/lib/public_suffix/domain.rb +235 -0
  304. data/vendor/bundle/ruby/3.2.0/gems/public_suffix-7.0.5/lib/public_suffix/errors.rb +41 -0
  305. data/vendor/bundle/ruby/3.2.0/gems/public_suffix-7.0.5/lib/public_suffix/list.rb +247 -0
  306. data/vendor/bundle/ruby/3.2.0/gems/public_suffix-7.0.5/lib/public_suffix/rule.rb +350 -0
  307. data/vendor/bundle/ruby/3.2.0/gems/public_suffix-7.0.5/lib/public_suffix/version.rb +14 -0
  308. data/vendor/bundle/ruby/3.2.0/gems/public_suffix-7.0.5/lib/public_suffix.rb +177 -0
  309. data/vendor/bundle/ruby/3.2.0/gems/racc-1.8.1/ext/racc/cparse/Makefile +3 -3
  310. data/vendor/bundle/ruby/3.2.0/gems/racc-1.8.1/lib/racc/cparse.so +0 -0
  311. data/vendor/bundle/ruby/3.2.0/gems/redcarpet-3.6.1/ext/redcarpet/Makefile +3 -3
  312. data/vendor/bundle/ruby/3.2.0/gems/redcarpet-3.6.1/lib/redcarpet.so +0 -0
  313. data/vendor/bundle/ruby/3.2.0/gems/rexml-3.4.4/LICENSE.txt +22 -0
  314. data/vendor/bundle/ruby/3.2.0/gems/rexml-3.4.4/NEWS.md +843 -0
  315. data/vendor/bundle/ruby/3.2.0/gems/rexml-3.4.4/README.md +57 -0
  316. data/vendor/bundle/ruby/3.2.0/gems/rexml-3.4.4/doc/rexml/context.rdoc +143 -0
  317. data/vendor/bundle/ruby/3.2.0/gems/rexml-3.4.4/doc/rexml/tasks/rdoc/child.rdoc +87 -0
  318. data/vendor/bundle/ruby/3.2.0/gems/rexml-3.4.4/doc/rexml/tasks/rdoc/document.rdoc +276 -0
  319. data/vendor/bundle/ruby/3.2.0/gems/rexml-3.4.4/doc/rexml/tasks/rdoc/element.rdoc +602 -0
  320. data/vendor/bundle/ruby/3.2.0/gems/rexml-3.4.4/doc/rexml/tasks/rdoc/node.rdoc +97 -0
  321. data/vendor/bundle/ruby/3.2.0/gems/rexml-3.4.4/doc/rexml/tasks/rdoc/parent.rdoc +267 -0
  322. data/vendor/bundle/ruby/3.2.0/gems/rexml-3.4.4/doc/rexml/tasks/tocs/child_toc.rdoc +12 -0
  323. data/vendor/bundle/ruby/3.2.0/gems/rexml-3.4.4/doc/rexml/tasks/tocs/document_toc.rdoc +30 -0
  324. data/vendor/bundle/ruby/3.2.0/gems/rexml-3.4.4/doc/rexml/tasks/tocs/element_toc.rdoc +55 -0
  325. data/vendor/bundle/ruby/3.2.0/gems/rexml-3.4.4/doc/rexml/tasks/tocs/master_toc.rdoc +135 -0
  326. data/vendor/bundle/ruby/3.2.0/gems/rexml-3.4.4/doc/rexml/tasks/tocs/node_toc.rdoc +16 -0
  327. data/vendor/bundle/ruby/3.2.0/gems/rexml-3.4.4/doc/rexml/tasks/tocs/parent_toc.rdoc +25 -0
  328. data/vendor/bundle/ruby/3.2.0/gems/rexml-3.4.4/doc/rexml/tutorial.rdoc +1358 -0
  329. data/vendor/bundle/ruby/3.2.0/gems/rexml-3.4.4/lib/rexml/attlistdecl.rb +63 -0
  330. data/vendor/bundle/ruby/3.2.0/gems/rexml-3.4.4/lib/rexml/attribute.rb +210 -0
  331. data/vendor/bundle/ruby/3.2.0/gems/rexml-3.4.4/lib/rexml/cdata.rb +68 -0
  332. data/vendor/bundle/ruby/3.2.0/gems/rexml-3.4.4/lib/rexml/child.rb +96 -0
  333. data/vendor/bundle/ruby/3.2.0/gems/rexml-3.4.4/lib/rexml/comment.rb +80 -0
  334. data/vendor/bundle/ruby/3.2.0/gems/rexml-3.4.4/lib/rexml/doctype.rb +306 -0
  335. data/vendor/bundle/ruby/3.2.0/gems/rexml-3.4.4/lib/rexml/document.rb +471 -0
  336. data/vendor/bundle/ruby/3.2.0/gems/rexml-3.4.4/lib/rexml/dtd/attlistdecl.rb +11 -0
  337. data/vendor/bundle/ruby/3.2.0/gems/rexml-3.4.4/lib/rexml/dtd/dtd.rb +47 -0
  338. data/vendor/bundle/ruby/3.2.0/gems/rexml-3.4.4/lib/rexml/dtd/elementdecl.rb +18 -0
  339. data/vendor/bundle/ruby/3.2.0/gems/rexml-3.4.4/lib/rexml/dtd/entitydecl.rb +57 -0
  340. data/vendor/bundle/ruby/3.2.0/gems/rexml-3.4.4/lib/rexml/dtd/notationdecl.rb +40 -0
  341. data/vendor/bundle/ruby/3.2.0/gems/rexml-3.4.4/lib/rexml/element.rb +2578 -0
  342. data/vendor/bundle/ruby/3.2.0/gems/rexml-3.4.4/lib/rexml/encoding.rb +48 -0
  343. data/vendor/bundle/ruby/3.2.0/gems/rexml-3.4.4/lib/rexml/entity.rb +142 -0
  344. data/vendor/bundle/ruby/3.2.0/gems/rexml-3.4.4/lib/rexml/formatters/default.rb +116 -0
  345. data/vendor/bundle/ruby/3.2.0/gems/rexml-3.4.4/lib/rexml/formatters/pretty.rb +142 -0
  346. data/vendor/bundle/ruby/3.2.0/gems/rexml-3.4.4/lib/rexml/formatters/transitive.rb +58 -0
  347. data/vendor/bundle/ruby/3.2.0/gems/rexml-3.4.4/lib/rexml/functions.rb +446 -0
  348. data/vendor/bundle/ruby/3.2.0/gems/rexml-3.4.4/lib/rexml/instruction.rb +79 -0
  349. data/vendor/bundle/ruby/3.2.0/gems/rexml-3.4.4/lib/rexml/light/node.rb +188 -0
  350. data/vendor/bundle/ruby/3.2.0/gems/rexml-3.4.4/lib/rexml/namespace.rb +63 -0
  351. data/vendor/bundle/ruby/3.2.0/gems/rexml-3.4.4/lib/rexml/node.rb +80 -0
  352. data/vendor/bundle/ruby/3.2.0/gems/rexml-3.4.4/lib/rexml/output.rb +30 -0
  353. data/vendor/bundle/ruby/3.2.0/gems/rexml-3.4.4/lib/rexml/parent.rb +166 -0
  354. data/vendor/bundle/ruby/3.2.0/gems/rexml-3.4.4/lib/rexml/parseexception.rb +53 -0
  355. data/vendor/bundle/ruby/3.2.0/gems/rexml-3.4.4/lib/rexml/parsers/baseparser.rb +949 -0
  356. data/vendor/bundle/ruby/3.2.0/gems/rexml-3.4.4/lib/rexml/parsers/lightparser.rb +59 -0
  357. data/vendor/bundle/ruby/3.2.0/gems/rexml-3.4.4/lib/rexml/parsers/pullparser.rb +213 -0
  358. data/vendor/bundle/ruby/3.2.0/gems/rexml-3.4.4/lib/rexml/parsers/sax2parser.rb +270 -0
  359. data/vendor/bundle/ruby/3.2.0/gems/rexml-3.4.4/lib/rexml/parsers/streamparser.rb +67 -0
  360. data/vendor/bundle/ruby/3.2.0/gems/rexml-3.4.4/lib/rexml/parsers/treeparser.rb +89 -0
  361. data/vendor/bundle/ruby/3.2.0/gems/rexml-3.4.4/lib/rexml/parsers/ultralightparser.rb +57 -0
  362. data/vendor/bundle/ruby/3.2.0/gems/rexml-3.4.4/lib/rexml/parsers/xpathparser.rb +739 -0
  363. data/vendor/bundle/ruby/3.2.0/gems/rexml-3.4.4/lib/rexml/quickpath.rb +267 -0
  364. data/vendor/bundle/ruby/3.2.0/gems/rexml-3.4.4/lib/rexml/rexml.rb +39 -0
  365. data/vendor/bundle/ruby/3.2.0/gems/rexml-3.4.4/lib/rexml/sax2listener.rb +98 -0
  366. data/vendor/bundle/ruby/3.2.0/gems/rexml-3.4.4/lib/rexml/security.rb +28 -0
  367. data/vendor/bundle/ruby/3.2.0/gems/rexml-3.4.4/lib/rexml/source.rb +388 -0
  368. data/vendor/bundle/ruby/3.2.0/gems/rexml-3.4.4/lib/rexml/streamlistener.rb +93 -0
  369. data/vendor/bundle/ruby/3.2.0/gems/rexml-3.4.4/lib/rexml/text.rb +420 -0
  370. data/vendor/bundle/ruby/3.2.0/gems/rexml-3.4.4/lib/rexml/undefinednamespaceexception.rb +9 -0
  371. data/vendor/bundle/ruby/3.2.0/gems/rexml-3.4.4/lib/rexml/validation/relaxng.rb +540 -0
  372. data/vendor/bundle/ruby/3.2.0/gems/rexml-3.4.4/lib/rexml/validation/validation.rb +144 -0
  373. data/vendor/bundle/ruby/3.2.0/gems/rexml-3.4.4/lib/rexml/validation/validationexception.rb +10 -0
  374. data/vendor/bundle/ruby/3.2.0/gems/rexml-3.4.4/lib/rexml/xmldecl.rb +130 -0
  375. data/vendor/bundle/ruby/3.2.0/gems/rexml-3.4.4/lib/rexml/xmltokens.rb +85 -0
  376. data/vendor/bundle/ruby/3.2.0/gems/rexml-3.4.4/lib/rexml/xpath.rb +70 -0
  377. data/vendor/bundle/ruby/3.2.0/gems/rexml-3.4.4/lib/rexml/xpath_parser.rb +980 -0
  378. data/vendor/bundle/ruby/3.2.0/gems/rexml-3.4.4/lib/rexml.rb +3 -0
  379. data/vendor/bundle/ruby/3.2.0/gems/stringio-3.2.0/ext/stringio/Makefile +3 -3
  380. data/vendor/bundle/ruby/3.2.0/gems/stringio-3.2.0/lib/stringio.so +0 -0
  381. data/vendor/bundle/ruby/3.2.0/gems/webmock-3.26.2/CHANGELOG.md +2148 -0
  382. data/vendor/bundle/ruby/3.2.0/gems/webmock-3.26.2/LICENSE +20 -0
  383. data/vendor/bundle/ruby/3.2.0/gems/webmock-3.26.2/README.md +1229 -0
  384. data/vendor/bundle/ruby/3.2.0/gems/webmock-3.26.2/lib/webmock/api.rb +111 -0
  385. data/vendor/bundle/ruby/3.2.0/gems/webmock-3.26.2/lib/webmock/assertion_failure.rb +13 -0
  386. data/vendor/bundle/ruby/3.2.0/gems/webmock-3.26.2/lib/webmock/callback_registry.rb +37 -0
  387. data/vendor/bundle/ruby/3.2.0/gems/webmock-3.26.2/lib/webmock/config.rb +20 -0
  388. data/vendor/bundle/ruby/3.2.0/gems/webmock-3.26.2/lib/webmock/cucumber.rb +12 -0
  389. data/vendor/bundle/ruby/3.2.0/gems/webmock-3.26.2/lib/webmock/deprecation.rb +11 -0
  390. data/vendor/bundle/ruby/3.2.0/gems/webmock-3.26.2/lib/webmock/errors.rb +19 -0
  391. data/vendor/bundle/ruby/3.2.0/gems/webmock-3.26.2/lib/webmock/http_lib_adapters/async_http_client_adapter.rb +228 -0
  392. data/vendor/bundle/ruby/3.2.0/gems/webmock-3.26.2/lib/webmock/http_lib_adapters/curb_adapter.rb +354 -0
  393. data/vendor/bundle/ruby/3.2.0/gems/webmock-3.26.2/lib/webmock/http_lib_adapters/em_http_request_adapter.rb +239 -0
  394. data/vendor/bundle/ruby/3.2.0/gems/webmock-3.26.2/lib/webmock/http_lib_adapters/excon_adapter.rb +167 -0
  395. data/vendor/bundle/ruby/3.2.0/gems/webmock-3.26.2/lib/webmock/http_lib_adapters/http_lib_adapter.rb +9 -0
  396. data/vendor/bundle/ruby/3.2.0/gems/webmock-3.26.2/lib/webmock/http_lib_adapters/http_lib_adapter_registry.rb +21 -0
  397. data/vendor/bundle/ruby/3.2.0/gems/webmock-3.26.2/lib/webmock/http_lib_adapters/http_rb/client.rb +17 -0
  398. data/vendor/bundle/ruby/3.2.0/gems/webmock-3.26.2/lib/webmock/http_lib_adapters/http_rb/request.rb +28 -0
  399. data/vendor/bundle/ruby/3.2.0/gems/webmock-3.26.2/lib/webmock/http_lib_adapters/http_rb/response.rb +95 -0
  400. data/vendor/bundle/ruby/3.2.0/gems/webmock-3.26.2/lib/webmock/http_lib_adapters/http_rb/streamer.rb +44 -0
  401. data/vendor/bundle/ruby/3.2.0/gems/webmock-3.26.2/lib/webmock/http_lib_adapters/http_rb/webmock.rb +77 -0
  402. data/vendor/bundle/ruby/3.2.0/gems/webmock-3.26.2/lib/webmock/http_lib_adapters/http_rb_adapter.rb +39 -0
  403. data/vendor/bundle/ruby/3.2.0/gems/webmock-3.26.2/lib/webmock/http_lib_adapters/httpclient_adapter.rb +260 -0
  404. data/vendor/bundle/ruby/3.2.0/gems/webmock-3.26.2/lib/webmock/http_lib_adapters/manticore_adapter.rb +147 -0
  405. data/vendor/bundle/ruby/3.2.0/gems/webmock-3.26.2/lib/webmock/http_lib_adapters/net_http.rb +310 -0
  406. data/vendor/bundle/ruby/3.2.0/gems/webmock-3.26.2/lib/webmock/http_lib_adapters/net_http_response.rb +36 -0
  407. data/vendor/bundle/ruby/3.2.0/gems/webmock-3.26.2/lib/webmock/http_lib_adapters/patron_adapter.rb +132 -0
  408. data/vendor/bundle/ruby/3.2.0/gems/webmock-3.26.2/lib/webmock/http_lib_adapters/typhoeus_hydra_adapter.rb +190 -0
  409. data/vendor/bundle/ruby/3.2.0/gems/webmock-3.26.2/lib/webmock/matchers/any_arg_matcher.rb +15 -0
  410. data/vendor/bundle/ruby/3.2.0/gems/webmock-3.26.2/lib/webmock/matchers/hash_argument_matcher.rb +23 -0
  411. data/vendor/bundle/ruby/3.2.0/gems/webmock-3.26.2/lib/webmock/matchers/hash_excluding_matcher.rb +17 -0
  412. data/vendor/bundle/ruby/3.2.0/gems/webmock-3.26.2/lib/webmock/matchers/hash_including_matcher.rb +19 -0
  413. data/vendor/bundle/ruby/3.2.0/gems/webmock-3.26.2/lib/webmock/minitest.rb +43 -0
  414. data/vendor/bundle/ruby/3.2.0/gems/webmock-3.26.2/lib/webmock/rack_response.rb +73 -0
  415. data/vendor/bundle/ruby/3.2.0/gems/webmock-3.26.2/lib/webmock/request_body_diff.rb +66 -0
  416. data/vendor/bundle/ruby/3.2.0/gems/webmock-3.26.2/lib/webmock/request_execution_verifier.rb +79 -0
  417. data/vendor/bundle/ruby/3.2.0/gems/webmock-3.26.2/lib/webmock/request_pattern.rb +428 -0
  418. data/vendor/bundle/ruby/3.2.0/gems/webmock-3.26.2/lib/webmock/request_registry.rb +37 -0
  419. data/vendor/bundle/ruby/3.2.0/gems/webmock-3.26.2/lib/webmock/request_signature.rb +56 -0
  420. data/vendor/bundle/ruby/3.2.0/gems/webmock-3.26.2/lib/webmock/request_signature_snippet.rb +63 -0
  421. data/vendor/bundle/ruby/3.2.0/gems/webmock-3.26.2/lib/webmock/request_stub.rb +134 -0
  422. data/vendor/bundle/ruby/3.2.0/gems/webmock-3.26.2/lib/webmock/response.rb +161 -0
  423. data/vendor/bundle/ruby/3.2.0/gems/webmock-3.26.2/lib/webmock/responses_sequence.rb +42 -0
  424. data/vendor/bundle/ruby/3.2.0/gems/webmock-3.26.2/lib/webmock/rspec/matchers/request_pattern_matcher.rb +80 -0
  425. data/vendor/bundle/ruby/3.2.0/gems/webmock-3.26.2/lib/webmock/rspec/matchers/webmock_matcher.rb +69 -0
  426. data/vendor/bundle/ruby/3.2.0/gems/webmock-3.26.2/lib/webmock/rspec/matchers.rb +29 -0
  427. data/vendor/bundle/ruby/3.2.0/gems/webmock-3.26.2/lib/webmock/rspec.rb +44 -0
  428. data/vendor/bundle/ruby/3.2.0/gems/webmock-3.26.2/lib/webmock/stub_registry.rb +84 -0
  429. data/vendor/bundle/ruby/3.2.0/gems/webmock-3.26.2/lib/webmock/stub_request_snippet.rb +40 -0
  430. data/vendor/bundle/ruby/3.2.0/gems/webmock-3.26.2/lib/webmock/test_unit.rb +22 -0
  431. data/vendor/bundle/ruby/3.2.0/gems/webmock-3.26.2/lib/webmock/util/hash_counter.rb +45 -0
  432. data/vendor/bundle/ruby/3.2.0/gems/webmock-3.26.2/lib/webmock/util/hash_keys_stringifier.rb +27 -0
  433. data/vendor/bundle/ruby/3.2.0/gems/webmock-3.26.2/lib/webmock/util/hash_validator.rb +19 -0
  434. data/vendor/bundle/ruby/3.2.0/gems/webmock-3.26.2/lib/webmock/util/headers.rb +77 -0
  435. data/vendor/bundle/ruby/3.2.0/gems/webmock-3.26.2/lib/webmock/util/parsers/json.rb +72 -0
  436. data/vendor/bundle/ruby/3.2.0/gems/webmock-3.26.2/lib/webmock/util/parsers/parse_error.rb +7 -0
  437. data/vendor/bundle/ruby/3.2.0/gems/webmock-3.26.2/lib/webmock/util/parsers/xml.rb +16 -0
  438. data/vendor/bundle/ruby/3.2.0/gems/webmock-3.26.2/lib/webmock/util/query_mapper.rb +283 -0
  439. data/vendor/bundle/ruby/3.2.0/gems/webmock-3.26.2/lib/webmock/util/uri.rb +113 -0
  440. data/vendor/bundle/ruby/3.2.0/gems/webmock-3.26.2/lib/webmock/util/values_stringifier.rb +22 -0
  441. data/vendor/bundle/ruby/3.2.0/gems/webmock-3.26.2/lib/webmock/util/version_checker.rb +113 -0
  442. data/vendor/bundle/ruby/3.2.0/gems/webmock-3.26.2/lib/webmock/version.rb +5 -0
  443. data/vendor/bundle/ruby/3.2.0/gems/webmock-3.26.2/lib/webmock/webmock.rb +175 -0
  444. data/vendor/bundle/ruby/3.2.0/gems/webmock-3.26.2/lib/webmock.rb +61 -0
  445. data/vendor/bundle/ruby/3.2.0/gems/websocket-driver-0.8.0/ext/websocket-driver/Makefile +3 -3
  446. data/vendor/bundle/ruby/3.2.0/gems/websocket-driver-0.8.0/lib/websocket_mask.so +0 -0
  447. data/vendor/bundle/ruby/3.2.0/specifications/addressable-2.9.0.gemspec +29 -0
  448. data/vendor/bundle/ruby/3.2.0/specifications/crack-1.0.1.gemspec +27 -0
  449. data/vendor/bundle/ruby/3.2.0/specifications/faraday-2.14.1.gemspec +0 -0
  450. data/vendor/bundle/ruby/3.2.0/specifications/faraday-net_http-3.4.2.gemspec +26 -0
  451. data/vendor/bundle/ruby/3.2.0/specifications/hashdiff-1.2.1.gemspec +30 -0
  452. data/vendor/bundle/ruby/3.2.0/specifications/net-http-0.9.1.gemspec +27 -0
  453. data/vendor/bundle/ruby/3.2.0/specifications/public_suffix-7.0.5.gemspec +24 -0
  454. data/vendor/bundle/ruby/3.2.0/specifications/rexml-3.4.4.gemspec +25 -0
  455. data/vendor/bundle/ruby/3.2.0/specifications/webmock-3.26.2.gemspec +44 -0
  456. metadata +377 -2
@@ -40,6 +40,11 @@ module IronAdmin
40
40
  # @see IronAdmin::Field For field configuration options
41
41
  # @see IronAdmin::Policy For authorization options
42
42
  class Resource
43
+ include Concerns::Nestable
44
+ include Concerns::SoftDeletable
45
+ include Concerns::Importable
46
+ include Concerns::LiveUpdatable
47
+
43
48
  class_attribute :field_overrides, default: {}
44
49
  class_attribute :_searchable_columns, default: nil
45
50
  class_attribute :_unsearchable_columns, default: []
@@ -57,6 +62,7 @@ module IronAdmin
57
62
  class_attribute :denied_crud_actions, default: []
58
63
  class_attribute :defined_associations, default: {}
59
64
  class_attribute :model_class_override, default: nil
65
+ class_attribute :adapter_class, default: :active_record
60
66
  class_attribute :_soft_delete_scopes, default: []
61
67
 
62
68
  class << self
@@ -72,6 +78,25 @@ module IronAdmin
72
78
  end
73
79
  end
74
80
 
81
+ # Returns the adapter instance for this resource's model.
82
+ #
83
+ # The adapter provides a uniform interface for schema introspection,
84
+ # query building, and CRUD operations regardless of the underlying
85
+ # data source (ActiveRecord, Mongoid, HTTP, etc.).
86
+ #
87
+ # Defensively re-resolves when the cached `@adapter` doesn't match
88
+ # the currently configured `adapter_class`. This catches the case
89
+ # where the eager `Resource.inherited` flow memoized an
90
+ # `Adapters::ActiveRecord` built before the subclass body's
91
+ # `self.adapter_class = :mongoid` (or `:http`) took effect.
92
+ #
93
+ # @return [IronAdmin::Adapters::Base] The adapter instance
94
+ def adapter
95
+ wanted = resolve_adapter_class
96
+ @adapter = nil if @adapter && !@adapter.instance_of?(wanted)
97
+ @adapter ||= wanted.new(model)
98
+ end
99
+
75
100
  # Returns the ActiveRecord model class for this resource.
76
101
  #
77
102
  # By default, infers the model from the resource class name
@@ -83,10 +108,19 @@ module IronAdmin
83
108
  # class LegacyUserResource < IronAdmin::Resource
84
109
  # self.model_class_override = OldUser
85
110
  # end
111
+ #
112
+ # @example HTTP adapter — no Ruby model required
113
+ # class ProductResource < IronAdmin::Resource
114
+ # self.adapter_class = :http
115
+ # # `model` returns `Adapters::Http::ModelProxy.new(self)` — no
116
+ # # `Product` constant required; fields come from the API
117
+ # # response on first access.
118
+ # end
86
119
  def model
87
120
  return model_class_override if model_class_override
121
+ return Adapters::Http::ModelProxy.new(self) if adapter_class == :http
88
122
 
89
- name.sub(/Resource\z/, "").constantize
123
+ name.sub(/Resource\z/, "").sub(/\AIronAdmin::Resources::/, "").constantize
90
124
  end
91
125
 
92
126
  # Configures display and behavior options for a field.
@@ -162,7 +196,7 @@ module IronAdmin
162
196
  def searchable_columns
163
197
  return _searchable_columns if _searchable_columns
164
198
 
165
- model.columns.select { |c| c.type.in?(%i[string text]) }
199
+ adapter.columns.select { |c| c.type.in?(%i[string text]) }
166
200
  .map { |c| c.name.to_sym }
167
201
  .reject { |name| name.to_s.end_with?("_digest") }
168
202
  .reject { |name| _unsearchable_columns.include?(name) }
@@ -206,19 +240,16 @@ module IronAdmin
206
240
  end
207
241
 
208
242
  # @api private
209
- # Returns filters automatically inferred from enum columns.
243
+ # Returns filters automatically inferred from enum columns and database column types.
244
+ #
245
+ # String/text columns auto-infer as :string filters.
246
+ # Integer/float/decimal columns auto-infer as :number filters.
247
+ # Enum columns auto-infer as :select filters.
210
248
  #
211
249
  # @return [Array<Hash>] Auto-generated filter configurations
212
250
  def auto_inferred_filters
213
- return [] unless model.respond_to?(:defined_enums)
214
-
215
- model.defined_enums.map do |name, values|
216
- {
217
- name: name.to_sym,
218
- type: :select,
219
- options: values.keys,
220
- }
221
- end
251
+ enum_filters = enum_auto_filters
252
+ enum_filters + infer_column_filters(enum_filters)
222
253
  end
223
254
 
224
255
  # Returns all filters (auto-inferred + manually defined).
@@ -281,8 +312,9 @@ module IronAdmin
281
312
  # end
282
313
  #
283
314
  # @return [void]
284
- def action(name, **options, &block)
285
- self.defined_actions = defined_actions + [{ name: name, block: block, **options }]
315
+ def action(name, form_fields: [], **options, &block)
316
+ coerced = coerce_action_fields(form_fields)
317
+ self.defined_actions = defined_actions + [{ name: name, block: block, form_fields: coerced, **options }]
286
318
  end
287
319
 
288
320
  # Defines a bulk action for multiple selected records.
@@ -310,8 +342,27 @@ module IronAdmin
310
342
  # end
311
343
  #
312
344
  # @return [void]
313
- def bulk_action(name, **options, &block)
314
- self.defined_bulk_actions = defined_bulk_actions + [{ name: name, block: block, **options }]
345
+ def bulk_action(name, form_fields: [], **options, &block)
346
+ coerced = coerce_action_fields(form_fields)
347
+ self.defined_bulk_actions = defined_bulk_actions + [{ name: name, block: block, form_fields: coerced, **options }]
348
+ end
349
+
350
+ # Convenience constructor for ActionField instances.
351
+ #
352
+ # @param name [Symbol] The field name
353
+ # @param opts [Hash] Options passed to ActionField.new
354
+ # @return [IronAdmin::ActionField]
355
+ #
356
+ # @example
357
+ # action :suspend,
358
+ # form_fields: [
359
+ # action_field(:reason, type: :textarea, required: true),
360
+ # action_field(:notify, type: :boolean)
361
+ # ] do |record, params|
362
+ # # ...
363
+ # end
364
+ def action_field(name, **)
365
+ ActionField.new(name: name, **)
315
366
  end
316
367
 
317
368
  # Specifies which fields appear on the index (list) page.
@@ -350,13 +401,25 @@ module IronAdmin
350
401
  # @option options [String] :icon Heroicon name for the menu item
351
402
  # @option options [String] :label Custom label (defaults to pluralized model name)
352
403
  # @option options [Integer] :priority Sort order (lower = higher in menu)
353
- # @option options [String] :section Group name for menu sections
404
+ # @option options [String] :group Group name for the sidebar section
405
+ # (sibling resources sharing the same `:group` are listed together;
406
+ # resources without a `:group` fall under "Resources"). `:section`
407
+ # is also accepted as an alias for `:group` for backward
408
+ # compatibility with earlier docs.
354
409
  #
355
410
  # @example
356
- # menu icon: "users", label: "Team Members", priority: 10
411
+ # menu icon: "users", label: "Team Members", priority: 10, group: "People"
357
412
  #
358
413
  # @return [void]
359
414
  def menu(**options)
415
+ if options.key?(:section)
416
+ legacy = options.delete(:section)
417
+ # Preserve `:group` precedence based on key presence so that
418
+ # explicitly passing `group: nil` (or `group: false`) still
419
+ # wins over `section:`. Using `||=` would clobber falsey
420
+ # explicit values.
421
+ options[:group] = legacy unless options.key?(:group)
422
+ end
360
423
  self.menu_options = options
361
424
  end
362
425
 
@@ -498,24 +561,7 @@ module IronAdmin
498
561
  # has_many :orders
499
562
  # has_many :comments, fields: [:id, :body, :created_at]
500
563
  #
501
- # @return [void]
502
- def has_many(name, **options)
503
- self.defined_associations = defined_associations.merge(name => { kind: :has_many, **options })
504
- end
505
-
506
- # Declares a has_one association for this resource.
507
- #
508
- # @param name [Symbol] The association name
509
- # @param options [Hash] Configuration options
510
- # @option options [Class] :resource The associated resource class
511
- #
512
- # @example
513
- # has_one :profile
514
- #
515
- # @return [void]
516
- def has_one(name, **options)
517
- self.defined_associations = defined_associations.merge(name => { kind: :has_one, **options })
518
- end
564
+ # has_many and has_one are defined in Concerns::Nestable
519
565
 
520
566
  # Declares a has_and_belongs_to_many association for this resource.
521
567
  #
@@ -541,7 +587,7 @@ module IronAdmin
541
587
  #
542
588
  # @return [Array<IronAdmin::Field>] Configured field objects
543
589
  def resolved_fields
544
- inferred = FieldInferrer.call(model)
590
+ inferred = FieldInferrer.call(adapter)
545
591
 
546
592
  inferred.map do |field|
547
593
  overrides = field_overrides[field.name] || {}
@@ -587,13 +633,13 @@ module IronAdmin
587
633
  defined_associations.filter_map do |assoc_name, config|
588
634
  next unless config[:kind] == :has_many
589
635
 
590
- reflection = model.reflect_on_association(assoc_name)
636
+ reflection = adapter.association(assoc_name)
591
637
  next unless reflection
592
638
 
593
- resource = ResourceRegistry.find(reflection.klass.model_name.plural)
639
+ resource = resolve_association_resource(config, reflection)
594
640
  next unless resource
595
641
 
596
- { name: assoc_name, reflection: reflection, resource: resource, **config.except(:kind) }
642
+ { name: assoc_name, reflection: reflection, resource: resource, **config.except(:kind, :resource) }
597
643
  end
598
644
  end
599
645
 
@@ -604,13 +650,13 @@ module IronAdmin
604
650
  defined_associations.filter_map do |assoc_name, config|
605
651
  next unless config[:kind] == :has_one
606
652
 
607
- reflection = model.reflect_on_association(assoc_name)
653
+ reflection = adapter.association(assoc_name)
608
654
  next unless reflection
609
655
 
610
- resource = ResourceRegistry.find(reflection.klass.model_name.plural)
656
+ resource = resolve_association_resource(config, reflection)
611
657
  next unless resource
612
658
 
613
- { name: assoc_name, reflection: reflection, resource: resource, **config.except(:kind) }
659
+ { name: assoc_name, reflection: reflection, resource: resource, **config.except(:kind, :resource) }
614
660
  end
615
661
  end
616
662
 
@@ -621,27 +667,25 @@ module IronAdmin
621
667
  defined_associations.filter_map do |assoc_name, config|
622
668
  next unless config[:kind] == :has_and_belongs_to_many
623
669
 
624
- reflection = model.reflect_on_association(assoc_name)
670
+ reflection = adapter.association(assoc_name)
625
671
  next unless reflection
626
672
 
627
- resource = ResourceRegistry.find(reflection.klass.model_name.plural)
673
+ resource = resolve_association_resource(config, reflection)
628
674
 
629
- { name: assoc_name, reflection: reflection, resource: resource, **config.except(:kind) }
675
+ { name: assoc_name, reflection: reflection, resource: resource, **config.except(:kind, :resource) }
630
676
  end
631
677
  end
632
678
 
633
679
  # Returns the URL-friendly resource name (pluralized model name).
634
680
  #
635
681
  # @return [String] The resource name (e.g., "users", "orders")
636
- def resource_name
637
- model.model_name.plural
638
- end
682
+ delegate :resource_name, to: :adapter
639
683
 
640
684
  # Returns the human-readable label for this resource.
641
685
  #
642
686
  # @return [String] Pluralized, humanized model name
643
687
  def label
644
- model.model_name.human.pluralize
688
+ adapter.human_name.pluralize
645
689
  end
646
690
 
647
691
  # Returns the attribute used to display individual records.
@@ -652,57 +696,71 @@ module IronAdmin
652
696
  # @return [Symbol] The display attribute name
653
697
  def display_attribute
654
698
  ApplicationHelper::DISPLAY_METHODS.find do |method|
655
- model.column_names.include?(method.to_s)
699
+ adapter.has_column?(method)
656
700
  end || :id
657
701
  end
658
702
 
659
- # Checks if the model supports soft delete.
660
- #
661
- # @return [Boolean] True if model has a deleted_at column
662
- def soft_delete?
663
- model.column_names.include?(soft_delete_column)
703
+ SKIP_FILTER_COLUMNS = %w[id created_at updated_at].freeze
704
+ private_constant :SKIP_FILTER_COLUMNS
705
+
706
+ private
707
+
708
+ # Resolves the adapter class from a symbol or class.
709
+ # Symbols are resolved via the Adapters::Registry (lazy loading).
710
+ # Classes are used directly (for custom adapters).
711
+ def resolve_adapter_class
712
+ return adapter_class unless adapter_class.is_a?(Symbol)
713
+
714
+ Adapters::Registry.resolve(adapter_class)
664
715
  end
665
716
 
666
- # Returns the column name used for soft delete tracking.
667
- #
668
- # @return [String] Column name (default: "deleted_at")
669
- def soft_delete_column
670
- "deleted_at"
717
+ # Resolves the resource class for an association configuration.
718
+ # Accepts Class, String, or falls back to ResourceRegistry lookup.
719
+ def resolve_association_resource(config, reflection)
720
+ resource = config[:resource]
721
+ return resource.constantize if resource.is_a?(String)
722
+ return resource if resource
723
+
724
+ ResourceRegistry.find(reflection.klass.model_name.plural)
671
725
  end
672
726
 
673
- # Registers soft delete scopes and restore action if model supports it.
674
- #
675
- # Automatically called during resource initialization when
676
- # a deleted_at column is detected.
677
- #
678
- # Registers:
679
- # - :with_deleted scope - Show all records including soft-deleted
680
- # - :only_deleted scope - Show only soft-deleted records
681
- # - :restore action - Restore a soft-deleted record
682
- #
683
- # @return [void]
684
- def register_soft_delete_features
685
- return unless soft_delete?
686
-
687
- # Capture the column name for use in lambdas
688
- column = soft_delete_column
689
- column_sym = column.to_sym
690
-
691
- # Store soft delete scopes separately so they always appear after user-defined scopes
692
- self._soft_delete_scopes = [
693
- { name: :with_deleted, scope: -> { unscope(where: column_sym) }, default: false },
694
- { name: :only_deleted, scope: -> { unscope(where: column_sym).where.not(column => nil) }, default: false },
695
- ]
696
-
697
- # Register restore action (only visible on soft-deleted records)
698
- action :restore, icon: "arrow-path", condition: ->(record) { record.public_send(column).present? } do |record|
699
- record.update(column => nil)
727
+ def enum_auto_filters
728
+ adapter.enums.map do |name, values|
729
+ {
730
+ name: name.to_sym,
731
+ type: :select,
732
+ options: values.keys,
733
+ }
700
734
  end
701
- rescue ActiveRecord::NoDatabaseError, ActiveRecord::StatementInvalid
702
- nil
703
735
  end
704
736
 
705
- private
737
+ def infer_column_filters(enum_filters)
738
+ already_defined = (defined_filters + enum_filters).map { |f| f[:name].to_sym }
739
+
740
+ adapter.columns.filter_map do |col|
741
+ next if already_defined.include?(col.name.to_sym)
742
+ next if SKIP_FILTER_COLUMNS.include?(col.name)
743
+
744
+ infer_filter_from_column(col)
745
+ end
746
+ end
747
+
748
+ def infer_filter_from_column(col)
749
+ case col.type
750
+ when :string, :text
751
+ { name: col.name.to_sym, type: :string, label: col.name.humanize }
752
+ when :integer, :float, :decimal
753
+ return nil if col.name.end_with?("_id")
754
+
755
+ { name: col.name.to_sym, type: :number, label: col.name.humanize }
756
+ end
757
+ end
758
+
759
+ def coerce_action_fields(fields)
760
+ Array(fields).map do |f|
761
+ f.is_a?(ActionField) ? f : ActionField.new(**f)
762
+ end
763
+ end
706
764
 
707
765
  # @api private
708
766
  def infer_preload_associations
@@ -21,14 +21,68 @@ module IronAdmin
21
21
  class << self
22
22
  # Registers a resource class in the registry.
23
23
  #
24
- # Called automatically when a Resource subclass is defined.
25
- # Also triggers soft delete feature registration if applicable.
24
+ # Called automatically by `Resource.inherited`. The class is added to
25
+ # the registry hash **before** any model introspection so that even
26
+ # if soft-delete registration raises, the resource is still
27
+ # discoverable (and will be retried by {.finalize!}).
28
+ #
29
+ # The eager soft-delete call fires with whatever `adapter_class` is
30
+ # set at inheritance time, which for non-AR resources is the parent
31
+ # default (`:active_record`) — that's wrong but expected. The error
32
+ # is swallowed and {.finalize!} re-runs with the correct adapter
33
+ # after the class body has finished evaluating.
26
34
  #
27
35
  # @param resource_class [Class] The resource class to register
28
36
  # @return [Class] The registered resource class
29
37
  def register(resource_class)
30
- resource_class.register_soft_delete_features
31
- resources[resource_class.resource_name] = resource_class
38
+ # Stage the registry key first so the resource is discoverable
39
+ # even if `register_soft_delete_features` raises. We use a key
40
+ # derived from the class name (no adapter/model lookup) so HTTP
41
+ # resources without a Ruby model class — and any other case where
42
+ # `resource_name` would touch a not-yet-loaded adapter — still
43
+ # land in the registry.
44
+ resources[registry_key_for(resource_class)] = resource_class
45
+ begin
46
+ resource_class.register_soft_delete_features
47
+ rescue StandardError
48
+ # Swallow eager-registration failures silently — `finalize!`
49
+ # retries with the correct adapter once the class body has
50
+ # fully evaluated, and logs there.
51
+ nil
52
+ end
53
+ resource_class
54
+ end
55
+
56
+ # Runs every post-load registration step on every registered resource.
57
+ #
58
+ # Called by the engine's `to_prepare` block once all resource classes
59
+ # have been eager-loaded and their bodies have fully evaluated. By
60
+ # then `adapter_class`, `model_class_override`, etc. are set
61
+ # correctly, so model introspection through the adapter is safe.
62
+ #
63
+ # Soft-delete registration is idempotent; resources that succeeded
64
+ # eagerly during {.register} are skipped here. Failures are logged
65
+ # and swallowed per resource so a single broken resource (missing
66
+ # model class, unreachable DB, etc.) doesn't take down the boot.
67
+ #
68
+ # The cached `@adapter` instance is invalidated for every resource
69
+ # before re-running registration. The eager `Resource.adapter` call
70
+ # at inheritance time memoized an `Adapters::ActiveRecord.new(model)`
71
+ # — built with whatever `adapter_class` defaulted to before the
72
+ # class body's `self.adapter_class = :mongoid` (or `:http`) had
73
+ # taken effect. Without this invalidation, every subsequent
74
+ # `Resource.adapter` would keep returning that stale AR adapter
75
+ # and Mongoid/HTTP resources would 500 with `undefined method
76
+ # 'columns' for <MongoidModel>`.
77
+ #
78
+ # @return [void]
79
+ def finalize!
80
+ all.each do |resource_class|
81
+ resource_class.instance_variable_set(:@adapter, nil)
82
+ resource_class.register_soft_delete_features
83
+ rescue StandardError => e
84
+ warn_finalize_failure(resource_class, e)
85
+ end
32
86
  end
33
87
 
34
88
  # Returns all registered resource classes.
@@ -89,6 +143,25 @@ module IronAdmin
89
143
  def resources
90
144
  @resources ||= {}
91
145
  end
146
+
147
+ # Builds the registry key without touching the adapter or model.
148
+ # Falls back from `resource_class.resource_name` (which delegates
149
+ # to the adapter and may raise on HTTP/Mongoid resources before
150
+ # `to_prepare` finalizes the lifecycle) to a name derived purely
151
+ # from the class identifier.
152
+ def registry_key_for(resource_class)
153
+ resource_class.resource_name
154
+ rescue StandardError
155
+ resource_class.name.demodulize.sub(/Resource\z/, "").underscore.pluralize
156
+ end
157
+
158
+ def warn_finalize_failure(resource_class, error)
159
+ return unless defined?(Rails) && Rails.respond_to?(:logger) && Rails.logger
160
+
161
+ Rails.logger.warn(
162
+ "[IronAdmin] Could not finalize #{resource_class.name}: #{error.class} (#{error.message})"
163
+ )
164
+ end
92
165
  end
93
166
  end
94
167
  end
@@ -1,12 +1,40 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module IronAdmin
4
+ # Base class for admin tools (custom pages in the admin panel).
5
+ #
6
+ # Tools are standalone pages that don't map to a database model.
7
+ # External gems can subclass Tool to add custom admin functionality.
8
+ #
9
+ # @example Simple tool with declarative actions
10
+ # class CacheManagerTool < IronAdmin::Tool
11
+ # menu icon: "server", label: "Cache Manager", group: "System"
12
+ #
13
+ # tool_action :clear_all, icon: "trash", confirm: true
14
+ # tool_action :flush_key,
15
+ # form_fields: [{ name: :cache_key, type: :text, required: true }]
16
+ #
17
+ # def clear_all(ctx)
18
+ # Rails.cache.clear
19
+ # ctx.flash[:notice] = "Cache cleared!"
20
+ # end
21
+ #
22
+ # def flush_key(ctx)
23
+ # key = ctx.action_params(:cache_key)[:cache_key]
24
+ # Rails.cache.delete(key)
25
+ # end
26
+ # end
4
27
  class Tool
5
28
  class_attribute :menu_options, default: {}
29
+ class_attribute :defined_tool_actions, default: []
30
+
31
+ # @return [ToolContext, nil] Request context, set by ToolsController before action execution
32
+ attr_accessor :context
6
33
 
7
34
  class << self
8
35
  def inherited(subclass)
9
36
  super
37
+ subclass.defined_tool_actions = defined_tool_actions.dup
10
38
  return if subclass.name.nil?
11
39
 
12
40
  begin
@@ -16,6 +44,28 @@ module IronAdmin
16
44
  end
17
45
  end
18
46
 
47
+ # Declares a tool action with metadata.
48
+ #
49
+ # @param name [Symbol] Method name to call when action is executed
50
+ # @param options [Hash] Action options passed to ToolAction.new
51
+ # @option options [String] :label Display label
52
+ # @option options [String] :icon Heroicon name
53
+ # @option options [Boolean] :confirm Show confirmation dialog
54
+ # @option options [String] :confirm_message Custom confirmation text
55
+ # @option options [Array<Hash, ActionField>] :form_fields Fields to collect before execution
56
+ # @option options [Proc] :condition Proc receiving user, returns boolean
57
+ def tool_action(name, **)
58
+ self.defined_tool_actions = defined_tool_actions + [ToolAction.new(name: name, **)]
59
+ end
60
+
61
+ # Finds a declared tool action by name.
62
+ #
63
+ # @param name [Symbol, String] Action name
64
+ # @return [ToolAction, nil]
65
+ def find_tool_action(name)
66
+ defined_tool_actions.find { |a| a.name == name.to_sym }
67
+ end
68
+
19
69
  def menu(**options)
20
70
  self.menu_options = options
21
71
  end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ module IronAdmin
4
+ # Value object representing a declared action on a Tool.
5
+ #
6
+ # Stores action metadata: display label, icon, confirmation settings,
7
+ # form fields to collect before execution, and authorization condition.
8
+ #
9
+ # @example
10
+ # ToolAction.new(
11
+ # name: :run_task,
12
+ # label: "Run Task",
13
+ # icon: "play",
14
+ # confirm: true,
15
+ # form_fields: [{ name: :task_name, type: :select, options: %w[db:migrate db:seed] }],
16
+ # condition: ->(user) { user.admin? }
17
+ # )
18
+ class ToolAction
19
+ # @return [Symbol] Action method name
20
+ attr_reader :name
21
+
22
+ # @return [String] Display label
23
+ attr_reader :label
24
+
25
+ # @return [String, nil] Heroicon name
26
+ attr_reader :icon
27
+
28
+ # @return [Boolean] Whether to show confirmation dialog
29
+ attr_reader :confirm
30
+
31
+ # @return [String, nil] Custom confirmation message
32
+ attr_reader :confirm_message
33
+
34
+ # @return [Array<ActionField>] Form fields to collect before execution
35
+ attr_reader :form_fields
36
+
37
+ # @return [Proc, nil] Condition proc that receives current_user, returns boolean
38
+ attr_reader :condition
39
+
40
+ def initialize(name:, label: nil, icon: nil, confirm: false, confirm_message: nil, form_fields: [], condition: nil)
41
+ @name = name.to_sym
42
+ @label = label || name.to_s.humanize
43
+ @icon = icon
44
+ @confirm = confirm
45
+ @confirm_message = confirm_message
46
+ @form_fields = coerce_form_fields(form_fields)
47
+ @condition = condition
48
+ end
49
+
50
+ # @return [Boolean] Whether this action has form fields to collect
51
+ def has_form? # rubocop:disable Naming/PredicatePrefix
52
+ form_fields.any?
53
+ end
54
+
55
+ # Checks if the action is allowed for the given user.
56
+ #
57
+ # @param user [Object, nil] Current user
58
+ # @return [Boolean]
59
+ def allowed?(user)
60
+ return true if condition.nil?
61
+
62
+ condition.call(user)
63
+ end
64
+
65
+ private
66
+
67
+ def coerce_form_fields(fields)
68
+ Array(fields).map do |f|
69
+ f.is_a?(ActionField) ? f : ActionField.new(**f)
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module IronAdmin
4
+ # Lightweight request context injected into Tool instances before action execution.
5
+ #
6
+ # Provides tools with access to request params, the current user, and flash messages
7
+ # without coupling them to the controller.
8
+ #
9
+ # @example Accessing context in a tool action
10
+ # class ReportTool < IronAdmin::Tool
11
+ # tool_action :generate
12
+ #
13
+ # def generate(ctx)
14
+ # user = ctx.current_user
15
+ # format = ctx.action_params(:format)[:format]
16
+ # ctx.flash[:notice] = "Report generated for #{user.name}"
17
+ # end
18
+ # end
19
+ class ToolContext
20
+ # @return [ActionController::Parameters] Raw request params
21
+ attr_reader :params
22
+
23
+ # @return [Object, nil] Current authenticated user
24
+ attr_reader :current_user
25
+
26
+ # @return [ActionDispatch::Flash::FlashHash] Flash messages
27
+ attr_reader :flash
28
+
29
+ # @param params [ActionController::Parameters]
30
+ # @param current_user [Object, nil]
31
+ # @param flash [ActionDispatch::Flash::FlashHash]
32
+ def initialize(params:, current_user:, flash:)
33
+ @params = params
34
+ @current_user = current_user
35
+ @flash = flash
36
+ end
37
+
38
+ # Extracts and permits only the specified keys from tool_action params.
39
+ #
40
+ # @param keys [Array<Symbol>] Keys to permit
41
+ # @return [Hash] Permitted params as a symbolized hash
42
+ def action_params(*keys)
43
+ raw = params.fetch(:tool_action, {})
44
+ return {} unless raw.respond_to?(:permit)
45
+
46
+ raw.permit(*keys).to_h.symbolize_keys
47
+ end
48
+ end
49
+ end
@@ -3,5 +3,5 @@
3
3
  module IronAdmin
4
4
  # The current version of the IronAdmin gem.
5
5
  # @return [String]
6
- VERSION = "0.4.1"
6
+ VERSION = "0.6.0"
7
7
  end