has_helpers 5.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 (748) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +38 -0
  3. data/.rspec +3 -0
  4. data/.rubocop.yml +10 -0
  5. data/.ruby-gemset +1 -0
  6. data/.ruby-version +1 -0
  7. data/.tekton/Chart.yaml +14 -0
  8. data/.tekton/templates/pipelinerun.yaml +44 -0
  9. data/.tekton/values-main.yaml +53 -0
  10. data/.tekton/values-pr.yaml +40 -0
  11. data/.tekton/values.yaml +17 -0
  12. data/.tool-versions +1 -0
  13. data/CHANGELOG.md +204 -0
  14. data/Gemfile +25 -0
  15. data/README.md +76 -0
  16. data/Rakefile +46 -0
  17. data/app/api/has_helpers/rack_api.rb +116 -0
  18. data/app/api/has_helpers/search_api.rb +173 -0
  19. data/app/assets/stylesheets/has_helpers/base.scss +5 -0
  20. data/app/assets/stylesheets/has_helpers/color_palette.scss +33 -0
  21. data/app/assets/stylesheets/has_helpers/color_palettes/default.scss +11 -0
  22. data/app/assets/stylesheets/has_helpers/components/dataset.scss +43 -0
  23. data/app/assets/stylesheets/has_helpers/mixins.scss +149 -0
  24. data/app/commands/has_helpers/cards/available_cards_command.rb +98 -0
  25. data/app/commands/has_helpers/cards/card_data_command.rb +1062 -0
  26. data/app/commands/reports/base_options.rb +79 -0
  27. data/app/commands/reports/belongs_to_options.rb +178 -0
  28. data/app/commands/reports/constant_options.rb +66 -0
  29. data/app/commands/reports/external_graphql_client.rb +116 -0
  30. data/app/commands/reports/fill_dates.rb +65 -0
  31. data/app/commands/reports/filter_option_generator.rb +186 -0
  32. data/app/commands/reports/generator.rb +310 -0
  33. data/app/commands/reports/has_many_options.rb +105 -0
  34. data/app/commands/reports/importer.rb +132 -0
  35. data/app/commands/reports/nested_attribute_options.rb +134 -0
  36. data/app/constants/color.rb +9 -0
  37. data/app/constants/file_format.rb +12 -0
  38. data/app/constants/has_helpers/cards/card_custom_field.rb +12 -0
  39. data/app/constants/has_helpers/cards/card_data_type.rb +11 -0
  40. data/app/constants/has_helpers/cards/card_identifier.rb +10 -0
  41. data/app/constants/has_helpers/cards/card_layout_type.rb +11 -0
  42. data/app/constants/has_helpers/cards/card_variant_type.rb +12 -0
  43. data/app/constants/has_helpers/link_type.rb +10 -0
  44. data/app/constants/has_helpers/notification_alert_status.rb +14 -0
  45. data/app/constants/has_helpers/notification_alert_type.rb +10 -0
  46. data/app/constants/has_helpers/operation.rb +11 -0
  47. data/app/constants/has_helpers/resource_type.rb +25 -0
  48. data/app/constants/icon.rb +22 -0
  49. data/app/constants/logical_operator.rb +7 -0
  50. data/app/constants/option.rb +10 -0
  51. data/app/constants/time_zone.rb +12 -0
  52. data/app/constructors/has_helpers/base_constructor.rb +62 -0
  53. data/app/controllers/concerns/has_helpers/service_trust/authenticated_as_service.rb +51 -0
  54. data/app/controllers/concerns/has_helpers/service_trust/graphql_authentication.rb +57 -0
  55. data/app/controllers/has_helpers/controller.rb +63 -0
  56. data/app/controllers/has_helpers/factory_controller.rb +261 -0
  57. data/app/controllers/has_helpers/graphql_extensions.rb +83 -0
  58. data/app/controllers/has_helpers/health_check_controller.rb +46 -0
  59. data/app/controllers/has_helpers/search_extensions.rb +92 -0
  60. data/app/controllers/has_helpers/service_trust/jwks_controller.rb +20 -0
  61. data/app/controllers/has_helpers/xlsx_export.rb +115 -0
  62. data/app/controllers/search_extensions.rb +5 -0
  63. data/app/graphql/has_helpers/base_type.rb +13 -0
  64. data/app/graphql/has_helpers/base_union.rb +9 -0
  65. data/app/graphql/has_helpers/enums/resource_enum.rb +24 -0
  66. data/app/graphql/has_helpers/middleware/application_access.rb +26 -0
  67. data/app/graphql/has_helpers/mutations/base.rb +27 -0
  68. data/app/graphql/has_helpers/mutations/external_data_sync.rb +72 -0
  69. data/app/graphql/has_helpers/mutations/mark_individual_alert_read.rb +31 -0
  70. data/app/graphql/has_helpers/mutations/mark_notifications_read.rb +50 -0
  71. data/app/graphql/has_helpers/mutations/toggle_favorite.rb +39 -0
  72. data/app/graphql/has_helpers/resolvers/app_nav.rb +107 -0
  73. data/app/graphql/has_helpers/resolvers/base.rb +27 -0
  74. data/app/graphql/has_helpers/resolvers/cards/available_cards.rb +31 -0
  75. data/app/graphql/has_helpers/resolvers/cards/card_data.rb +74 -0
  76. data/app/graphql/has_helpers/resolvers/cards/hover_card_data.rb +153 -0
  77. data/app/graphql/has_helpers/resolvers/favorite.rb +62 -0
  78. data/app/graphql/has_helpers/resolvers/internal/roles.rb +54 -0
  79. data/app/graphql/has_helpers/resolvers/is_favorite.rb +24 -0
  80. data/app/graphql/has_helpers/resolvers/list_notification_alerts.rb +47 -0
  81. data/app/graphql/has_helpers/resolvers/lookup.rb +36 -0
  82. data/app/graphql/has_helpers/resolvers/recently_viewed.rb +54 -0
  83. data/app/graphql/has_helpers/resolvers/resource_tab.rb +22 -0
  84. data/app/graphql/has_helpers/resolvers/search.rb +18 -0
  85. data/app/graphql/has_helpers/resolvers/search_query.rb +38 -0
  86. data/app/graphql/has_helpers/resolvers/user_application_order.rb +35 -0
  87. data/app/graphql/has_helpers/resources/organization_option_resource.rb +28 -0
  88. data/app/graphql/has_helpers/resources/organization_resource_resource.rb +18 -0
  89. data/app/graphql/has_helpers/resources/resource_group_resource.rb +10 -0
  90. data/app/graphql/has_helpers/resources/resource_resource.rb +21 -0
  91. data/app/graphql/has_helpers/resources/user_role_resource.rb +9 -0
  92. data/app/graphql/has_helpers/types/application_order_type.rb +18 -0
  93. data/app/graphql/has_helpers/types/cards/available_card_type.rb +20 -0
  94. data/app/graphql/has_helpers/types/cards/card_badge_type.rb +12 -0
  95. data/app/graphql/has_helpers/types/cards/card_config_type.rb +18 -0
  96. data/app/graphql/has_helpers/types/cards/card_data_type.rb +13 -0
  97. data/app/graphql/has_helpers/types/cards/card_entry_field_type.rb +21 -0
  98. data/app/graphql/has_helpers/types/cards/card_entry_type.rb +12 -0
  99. data/app/graphql/has_helpers/types/cards/card_field_config_type.rb +17 -0
  100. data/app/graphql/has_helpers/types/layouts/app_nav/link_section_type.rb +17 -0
  101. data/app/graphql/has_helpers/types/layouts/app_nav/link_type.rb +20 -0
  102. data/app/graphql/has_helpers/types/layouts/app_nav/sub_link_type.rb +17 -0
  103. data/app/graphql/has_helpers/types/layouts/app_nav/type.rb +16 -0
  104. data/app/graphql/has_helpers/types/lookup.rb +16 -0
  105. data/app/graphql/has_helpers/types/resource_tab/type.rb +15 -0
  106. data/app/graphql/has_helpers/types/role_type.rb +13 -0
  107. data/app/graphql/has_helpers/types/search/detail_badge_type.rb +15 -0
  108. data/app/graphql/has_helpers/types/search/detail_item_type.rb +14 -0
  109. data/app/graphql/has_helpers/types/search/detail_type.rb +16 -0
  110. data/app/graphql/has_helpers/types/search/filter_operation_enum.rb +19 -0
  111. data/app/graphql/has_helpers/types/search/input_type.rb +16 -0
  112. data/app/graphql/has_helpers/types/search/result_type.rb +72 -0
  113. data/app/graphql/has_helpers/types/search/search_filter_input_type.rb +17 -0
  114. data/app/graphql/has_helpers/types/search/type.rb +17 -0
  115. data/app/importers/has_helpers/cards/importer.rb +1223 -0
  116. data/app/lib/has_helpers/application_helper.rb +21 -0
  117. data/app/lib/has_helpers/dn/processor.rb +55 -0
  118. data/app/lib/has_helpers/dn/refresh.rb +13 -0
  119. data/app/lib/has_helpers/dn/utils/backfill_utils.rb +80 -0
  120. data/app/lib/has_helpers/dn/utils/query_utils.rb +216 -0
  121. data/app/lib/has_helpers/feature.rb +88 -0
  122. data/app/lib/has_helpers/hq_exception.rb +18 -0
  123. data/app/lib/has_helpers/index.rb +890 -0
  124. data/app/lib/has_helpers/organization_merge.rb +128 -0
  125. data/app/lib/has_helpers/query/elasticsearch.rb +20 -0
  126. data/app/lib/has_helpers/query/provider.rb +105 -0
  127. data/app/lib/has_helpers/query/sql.rb +133 -0
  128. data/app/lib/has_helpers/query.rb +14 -0
  129. data/app/lib/has_helpers/search/elasticsearch.rb +41 -0
  130. data/app/lib/has_helpers/search/elasticsearch_shared.rb +70 -0
  131. data/app/lib/has_helpers/search/provider.rb +149 -0
  132. data/app/lib/has_helpers/search/search_utils.rb +74 -0
  133. data/app/lib/has_helpers/search/sql_search.rb +219 -0
  134. data/app/lib/has_helpers/search.rb +46 -0
  135. data/app/models/ability.rb +233 -0
  136. data/app/models/has_helpers/application.rb +47 -0
  137. data/app/models/has_helpers/application_record.rb +7 -0
  138. data/app/models/has_helpers/cards/card_builder/base.rb +74 -0
  139. data/app/models/has_helpers/cards/card_builder/card_definition_builder.rb +486 -0
  140. data/app/models/has_helpers/cards/card_builder/definition_context.rb +82 -0
  141. data/app/models/has_helpers/cards/card_builder/field_builder.rb +142 -0
  142. data/app/models/has_helpers/cards/card_builder/resource_builder.rb +34 -0
  143. data/app/models/has_helpers/cards/card_definition.rb +41 -0
  144. data/app/models/has_helpers/cards/card_definition_resource.rb +19 -0
  145. data/app/models/has_helpers/cards/card_field.rb +39 -0
  146. data/app/models/has_helpers/cards/registry.rb +115 -0
  147. data/app/models/has_helpers/cards/role_card_field.rb +28 -0
  148. data/app/models/has_helpers/cards/role_resource_card.rb +41 -0
  149. data/app/models/has_helpers/dn_utils.rb +61 -0
  150. data/app/models/has_helpers/favorite.rb +20 -0
  151. data/app/models/has_helpers/master_record.rb +18 -0
  152. data/app/models/has_helpers/notification_alert.rb +52 -0
  153. data/app/models/has_helpers/organization.rb +16 -0
  154. data/app/models/has_helpers/organization_link.rb +92 -0
  155. data/app/models/has_helpers/organization_methods.rb +62 -0
  156. data/app/models/has_helpers/organization_option.rb +31 -0
  157. data/app/models/has_helpers/organization_resource.rb +45 -0
  158. data/app/models/has_helpers/pending_db_operation.rb +31 -0
  159. data/app/models/has_helpers/recently_viewed.rb +34 -0
  160. data/app/models/has_helpers/reindex_queue.rb +43 -0
  161. data/app/models/has_helpers/resource.rb +38 -0
  162. data/app/models/has_helpers/resource_group.rb +15 -0
  163. data/app/models/has_helpers/restriction.rb +15 -0
  164. data/app/models/has_helpers/restriction_operation.rb +13 -0
  165. data/app/models/has_helpers/role.rb +20 -0
  166. data/app/models/has_helpers/service_trust/audit_log.rb +20 -0
  167. data/app/models/has_helpers/service_trust/key.rb +56 -0
  168. data/app/models/has_helpers/stat.rb +13 -0
  169. data/app/models/has_helpers/user.rb +18 -0
  170. data/app/models/has_helpers/user_methods.rb +184 -0
  171. data/app/models/has_helpers/user_organization.rb +33 -0
  172. data/app/models/has_helpers/user_role.rb +27 -0
  173. data/app/models/has_helpers/user_session.rb +19 -0
  174. data/app/presenters/has_helpers/barcode_base_presenter.rb +113 -0
  175. data/app/presenters/has_helpers/base_presenter.rb +100 -0
  176. data/app/presenters/has_helpers/cacheable_item.rb +28 -0
  177. data/app/presenters/has_helpers/item_presenter.rb +52 -0
  178. data/app/presenters/has_helpers/items_presenter.rb +33 -0
  179. data/app/presenters/has_helpers/resource/action_presenter.rb +8 -0
  180. data/app/presenters/has_helpers/resource/cell_presenter.rb +52 -0
  181. data/app/presenters/has_helpers/resource/email_presenter.rb +34 -0
  182. data/app/presenters/has_helpers/resource/form_presenter.rb +76 -0
  183. data/app/presenters/has_helpers/resource/info_collection_presenter.rb +33 -0
  184. data/app/presenters/has_helpers/resource/info_presenter.rb +34 -0
  185. data/app/presenters/has_helpers/resource/list_presenter.rb +120 -0
  186. data/app/presenters/has_helpers/resource/note_presenter.rb +14 -0
  187. data/app/presenters/has_helpers/resource/row_presenter.rb +25 -0
  188. data/app/presenters/has_helpers/resource/tag_presenter.rb +31 -0
  189. data/app/presenters/has_helpers/search_presenter.rb +230 -0
  190. data/app/triggers/has_helpers/erase_denormalized_belongs_to_column_trigger.rb +85 -0
  191. data/app/triggers/has_helpers/limit_favorites.rb +97 -0
  192. data/app/utils/has_helpers/redis_configurable.rb +79 -0
  193. data/app/utils/has_helpers/sync_utils.rb +234 -0
  194. data/app/views/has_helpers/actions/_action.html.erb +31 -0
  195. data/app/views/has_helpers/alerts/_alert.html.erb +3 -0
  196. data/app/views/has_helpers/barcodes.html.erb +36 -0
  197. data/app/views/has_helpers/containers/_container.html.erb +3 -0
  198. data/app/views/has_helpers/email_notifier/system.html.erb +8 -0
  199. data/app/views/has_helpers/email_notifier/system.text.erb +7 -0
  200. data/app/views/has_helpers/enumerables/_enumerable.html.erb +1 -0
  201. data/app/views/has_helpers/icons/_icon.html.erb +1 -0
  202. data/app/views/has_helpers/render_objects/_render_object.html.erb +1 -0
  203. data/app/views/has_helpers/services/_service.html.erb +1 -0
  204. data/app/views/has_helpers/text_nodes/_text_node.html.erb +4 -0
  205. data/app/views/has_helpers/ui_attributes/_html_attrs.html.erb +1 -0
  206. data/app/workers/elasticsearch_process_queue.rb +11 -0
  207. data/app/workers/elasticsearch_reindex.rb +11 -0
  208. data/app/workers/elasticsearch_reindex_scheduler.rb +35 -0
  209. data/app/workers/good_job/configurable_backfill.rb +64 -0
  210. data/app/workers/good_job/cron_denormalization_stats_manager.rb +39 -0
  211. data/app/workers/good_job/denormalization_stats_worker.rb +57 -0
  212. data/app/workers/good_job/dn_update_worker.rb +30 -0
  213. data/app/workers/good_job/dn_worker.rb +47 -0
  214. data/app/workers/good_job/elasticsearch_old_index_cleaner_worker.rb +113 -0
  215. data/app/workers/good_job/elasticsearch_on_finish_reindex.rb +26 -0
  216. data/app/workers/good_job/elasticsearch_process_batch.rb +78 -0
  217. data/app/workers/good_job/elasticsearch_process_change_logs_master_worker.rb +27 -0
  218. data/app/workers/good_job/elasticsearch_process_change_logs_worker.rb +155 -0
  219. data/app/workers/good_job/elasticsearch_process_queue.rb +50 -0
  220. data/app/workers/good_job/elasticsearch_reindex.rb +31 -0
  221. data/app/workers/has_helpers/cards/orphan_card_sweeper.rb +28 -0
  222. data/app/workers/has_helpers/db_operations/clean_stats_worker.rb +42 -0
  223. data/app/workers/has_helpers/db_operations/helper_worker.rb +131 -0
  224. data/app/workers/has_helpers/db_operations/main_worker.rb +164 -0
  225. data/app/workers/has_helpers/db_operations/operations.rb +134 -0
  226. data/app/workers/has_helpers/db_operations/update_operations_stats_resume_worker.rb +35 -0
  227. data/app/workers/has_helpers/default_worker.rb +17 -0
  228. data/app/workers/has_helpers/good_job/notification_alert_cleanup_worker.rb +43 -0
  229. data/app/workers/has_helpers/service_trust/audit_log_cleanup_worker.rb +19 -0
  230. data/app/workers/sidekiq/elasticsearch_old_index_cleaner_worker.rb +112 -0
  231. data/app/workers/sidekiq/elasticsearch_process_batch.rb +87 -0
  232. data/app/workers/sidekiq/elasticsearch_process_queue.rb +40 -0
  233. data/app/workers/sidekiq/elasticsearch_reindex.rb +53 -0
  234. data/config/initializers/action_view.rb +7 -0
  235. data/config/initializers/inflections.rb +24 -0
  236. data/config/initializers/marginalia.rb +8 -0
  237. data/config/initializers/mime_types.rb +6 -0
  238. data/config/initializers/opentelemetry.rb +12 -0
  239. data/config/initializers/redis_conf.rb +40 -0
  240. data/config/initializers/searchkick.rb +9 -0
  241. data/config/initializers/will_paginate.rb +39 -0
  242. data/config/redis.yml +12 -0
  243. data/config/routes.rb +27 -0
  244. data/config/service_trust_routes.rb +5 -0
  245. data/config.ru +9 -0
  246. data/db/migrate/20230127194725_create_organization_table.rb +25 -0
  247. data/db/migrate/20230127194726_validate_organization_foreign_keys.rb +7 -0
  248. data/db/migrate/20230206141413_create_security_models.rb +42 -0
  249. data/db/migrate/20230206141513_create_restriction_model.rb +20 -0
  250. data/db/migrate/20230206142613_add_security_indexes.rb +14 -0
  251. data/db/migrate/20230206143513_security_model_constants_seed.rb +8 -0
  252. data/db/migrate/20230206161513_add_organization_to_security_models.rb +13 -0
  253. data/db/migrate/20230209121019_create_search_group_models.rb +36 -0
  254. data/db/migrate/20230209122919_add_search_group_indexes.rb +21 -0
  255. data/db/migrate/20230209181832_add_search_group_resources_type_constraint_trigger.rb +61 -0
  256. data/db/migrate/20230313220337_create_table_user_sessions.rb +31 -0
  257. data/db/migrate/20230517141106_update_resource_types.rb +12 -0
  258. data/db/migrate/20230518194611_add_resources_contraints.rb +17 -0
  259. data/db/migrate/20230518194642_create_nested_resource_model.rb +21 -0
  260. data/db/migrate/20230518194645_validate_security_models_constraints.rb +23 -0
  261. data/db/migrate/20230608125037_add_inactive_field_to_user_sessions.rb +7 -0
  262. data/db/migrate/20230919191525_add_parent_id_to_resources.rb +8 -0
  263. data/db/migrate/20230919191535_validate_parent_id_fk_resources.rb +7 -0
  264. data/db/migrate/20230919210456_drop_nested_resources.rb +7 -0
  265. data/db/migrate/20231222135620_add_user_role.rb +19 -0
  266. data/db/migrate/20240108172021_create_favorites_table.rb +20 -0
  267. data/db/migrate/20240108190209_create_organization_link_table_if_doesnt_exists.rb +31 -0
  268. data/db/migrate/20240109211445_validate_fks_org_links.rb +9 -0
  269. data/db/migrate/20240110135440_create_recently_vieweds.rb +20 -0
  270. data/db/migrate/20240112185312_link_type_options_seed.rb +7 -0
  271. data/db/migrate/20240116173201_organization_link_resource_tab_constraints.rb +19 -0
  272. data/db/migrate/20240131160949_add_user_organizations_table.rb +32 -0
  273. data/db/migrate/20240212132519_add_organization_in_user_sessions.rb +13 -0
  274. data/db/migrate/20240227135052_add_user_organization_composite_fks.rb +32 -0
  275. data/db/migrate/20240312193501_update_organization_links.rb +11 -0
  276. data/db/migrate/20240426185902_add_user_organizations_current_organization_column.rb +12 -0
  277. data/db/migrate/20240429214601_add_primary_trigger.rb +10 -0
  278. data/db/migrate/20241112131819_add_json_file_format_seed.rb +7 -0
  279. data/db/migrate/20250520133417_create_restriction_operations.rb +18 -0
  280. data/db/migrate/20250520133420_validate_create_restriction_operations.rb +8 -0
  281. data/db/migrate/20250520140642_add_indexes_to_restrictions.rb +9 -0
  282. data/db/migrate/20250529130741_create_notification_alerts_table.rb +28 -0
  283. data/db/migrate/20250530223332_create_data_migrations_table.rb +19 -0
  284. data/db/migrate/20250605121622_seed_notification_alert_status.rb +7 -0
  285. data/db/migrate/20250605131439_seed_notification_alert_types.rb +7 -0
  286. data/db/migrate/20250606173128_update_notification_alert_length_constraint.rb +18 -0
  287. data/db/migrate/20250606174043_validate_notification_alert_length_constraints.rb +8 -0
  288. data/db/migrate/20250624183000_add_data_import_alert_type.rb +11 -0
  289. data/db/migrate/20250722183935_create_organization_options_table.rb +38 -0
  290. data/db/migrate/20250722192724_validate_organization_options_fks_and_constraints.rb +32 -0
  291. data/db/migrate/20250722192936_add_indexes_to_organization_options_table.rb +15 -0
  292. data/db/migrate/20250915132448_add_column_restrictions_hash.rb +7 -0
  293. data/db/migrate/20250917143211_add_column_permissions_hash.rb +7 -0
  294. data/db/migrate/20251001145950_add_trust_this_device.rb +7 -0
  295. data/db/migrate/20251021130835_add_backend_urls_to_applications_table.rb +13 -0
  296. data/db/migrate/20251021172715_validate_backend_urls_constraints_in_applications.rb +9 -0
  297. data/db/migrate/20251030200020_create_card_definitions.rb +23 -0
  298. data/db/migrate/20251030201012_create_card_definition_resources.rb +24 -0
  299. data/db/migrate/20251030201236_create_card_fields.rb +34 -0
  300. data/db/migrate/20251103194317_create_role_resource_cards.rb +37 -0
  301. data/db/migrate/20251103194426_create_role_card_fields.rb +34 -0
  302. data/db/migrate/20251107173218_validate_columns_from_cards.rb +17 -0
  303. data/db/migrate/20251111173212_add_column_application_array.rb +7 -0
  304. data/db/migrate/20251112203326_add_card_types.rb +10 -0
  305. data/db/migrate/20251115120000_add_virtual_cards_support.rb +28 -0
  306. data/db/migrate/20251202231256_add_org_scoped_fk_role_resource_cards_to_roles.rb +14 -0
  307. data/db/migrate/20260105134504_add_accessor_field_resource_type.rb +7 -0
  308. data/db/migrate/20260115123000_add_default_visibility_to_card_definitions.rb +9 -0
  309. data/db/migrate/20260120175037_is_new_admin.rb +9 -0
  310. data/db/migrate/20260122392008_add_stats_table.rb +17 -0
  311. data/db/migrate/20260414204718_add_index_to_stats_table.rb +10 -0
  312. data/db/migrate/20260509120000_create_service_trust_keys.rb +25 -0
  313. data/db/migrate/20260509120001_create_service_trust_audit_logs.rb +26 -0
  314. data/db/migrate/20260511120000_add_direction_to_service_trust_audit_logs.rb +17 -0
  315. data/db/migrate/20260511121000_remove_role_fk_from_role_resource_cards.rb +19 -0
  316. data/db/migrate/20260511121001_add_disabled_at_to_role_resource_cards.rb +10 -0
  317. data/db/migrate/20260605120100_seed_hover_card_options.rb +8 -0
  318. data/db/migrate/20260608120000_seed_badge_list_card_data_type.rb +7 -0
  319. data/db/migrate/20260727000000_add_two_factor_trust_id_to_user_sessions.rb +11 -0
  320. data/db/seeds.rb +14 -0
  321. data/docs/Elasticsearch.md +23 -0
  322. data/docs/Search_API.md +126 -0
  323. data/docs/cards_role_proxy.md +107 -0
  324. data/docs/factory_api.md +135 -0
  325. data/has-helpers-pr-289-pipeline.yaml +16 -0
  326. data/has-helpers-pr-298-pipeline.yaml +15 -0
  327. data/has_helpers.gemspec +68 -0
  328. data/lib/active_record/migration/active_migration_patch.rb +123 -0
  329. data/lib/generators/has_helpers/install/USAGE +8 -0
  330. data/lib/generators/has_helpers/install/install_generator.rb +22 -0
  331. data/lib/generators/has_helpers/install/templates/migrate/has_helpers_initial_setup.rb +26 -0
  332. data/lib/has_helpers/active_record/base.rb +50 -0
  333. data/lib/has_helpers/active_record/date_range.rb +228 -0
  334. data/lib/has_helpers/active_record/nested_resource.rb +58 -0
  335. data/lib/has_helpers/active_record/validation.rb +63 -0
  336. data/lib/has_helpers/active_record.rb +520 -0
  337. data/lib/has_helpers/app/command.rb +107 -0
  338. data/lib/has_helpers/app.rb +10 -0
  339. data/lib/has_helpers/assets_precompile_helper.rb +20 -0
  340. data/lib/has_helpers/attributes.rb +196 -0
  341. data/lib/has_helpers/aws.rb +67 -0
  342. data/lib/has_helpers/cards/cache.rb +49 -0
  343. data/lib/has_helpers/cards/config.rb +42 -0
  344. data/lib/has_helpers/cards/remote_role.rb +27 -0
  345. data/lib/has_helpers/cards/remote_roles.rb +75 -0
  346. data/lib/has_helpers/cards/roles.rb +55 -0
  347. data/lib/has_helpers/cards.rb +34 -0
  348. data/lib/has_helpers/concerns/hydrate_concern.rb +76 -0
  349. data/lib/has_helpers/concerns/importable_accessors.rb +25 -0
  350. data/lib/has_helpers/concerns/restrictions_parseable.rb +51 -0
  351. data/lib/has_helpers/config.rb +11 -0
  352. data/lib/has_helpers/constant.rb +578 -0
  353. data/lib/has_helpers/constraints.rb +10 -0
  354. data/lib/has_helpers/core_ext/array.rb +28 -0
  355. data/lib/has_helpers/core_ext/date/infinity.rb +25 -0
  356. data/lib/has_helpers/core_ext/date.rb +39 -0
  357. data/lib/has_helpers/core_ext/enumerable.rb +20 -0
  358. data/lib/has_helpers/core_ext/false_class.rb +14 -0
  359. data/lib/has_helpers/core_ext/float.rb +18 -0
  360. data/lib/has_helpers/core_ext/hash.rb +71 -0
  361. data/lib/has_helpers/core_ext/integer.rb +18 -0
  362. data/lib/has_helpers/core_ext/nil_class.rb +30 -0
  363. data/lib/has_helpers/core_ext/object.rb +60 -0
  364. data/lib/has_helpers/core_ext/proc.rb +16 -0
  365. data/lib/has_helpers/core_ext/string.rb +81 -0
  366. data/lib/has_helpers/core_ext/symbol.rb +17 -0
  367. data/lib/has_helpers/core_ext/true_class.rb +14 -0
  368. data/lib/has_helpers/cursor.rb +46 -0
  369. data/lib/has_helpers/data_migration/base.rb +293 -0
  370. data/lib/has_helpers/data_migration/large_worker.rb +20 -0
  371. data/lib/has_helpers/data_migration/master_worker.rb +33 -0
  372. data/lib/has_helpers/data_migration/migrator.rb +83 -0
  373. data/lib/has_helpers/data_migration/model.rb +18 -0
  374. data/lib/has_helpers/data_migration/small_worker.rb +20 -0
  375. data/lib/has_helpers/data_migration.rb +8 -0
  376. data/lib/has_helpers/engine.rb +47 -0
  377. data/lib/has_helpers/extensions.rb +17 -0
  378. data/lib/has_helpers/factory_api.rb +47 -0
  379. data/lib/has_helpers/factory_scenario/definition.rb +33 -0
  380. data/lib/has_helpers/factory_scenario/dsl.rb +80 -0
  381. data/lib/has_helpers/factory_scenario/factory_bot_override.rb +53 -0
  382. data/lib/has_helpers/factory_scenario/registry.rb +31 -0
  383. data/lib/has_helpers/factory_scenario/runner.rb +285 -0
  384. data/lib/has_helpers/factory_scenario.rb +27 -0
  385. data/lib/has_helpers/fixed_expiration_time_cache_session_store.rb +20 -0
  386. data/lib/has_helpers/flattener/association.rb +140 -0
  387. data/lib/has_helpers/flattener/attribute.rb +42 -0
  388. data/lib/has_helpers/flattener/base.rb +428 -0
  389. data/lib/has_helpers/flattener/column/types.rb +71 -0
  390. data/lib/has_helpers/flattener/column.rb +97 -0
  391. data/lib/has_helpers/flattener/pivot.rb +49 -0
  392. data/lib/has_helpers/flattener/reset.rb +33 -0
  393. data/lib/has_helpers/flattener/resource.rb +114 -0
  394. data/lib/has_helpers/flattener/resource_generator.rb +21 -0
  395. data/lib/has_helpers/flattener/utils.rb +14 -0
  396. data/lib/has_helpers/flattener.rb +53 -0
  397. data/lib/has_helpers/hash_accessors.rb +62 -0
  398. data/lib/has_helpers/helpers/application.rb +291 -0
  399. data/lib/has_helpers/index/config.rb +15 -0
  400. data/lib/has_helpers/lazy_config.rb +61 -0
  401. data/lib/has_helpers/resource_importer.rb +260 -0
  402. data/lib/has_helpers/resources.rb +127 -0
  403. data/lib/has_helpers/rspec/constants.rb +61 -0
  404. data/lib/has_helpers/rspec/factories.rb +11 -0
  405. data/lib/has_helpers/rspec.rb +22 -0
  406. data/lib/has_helpers/sentry/graphql_trace.rb +37 -0
  407. data/lib/has_helpers/serializer.rb +41 -0
  408. data/lib/has_helpers/service_trust/audit.rb +45 -0
  409. data/lib/has_helpers/service_trust/authorization.rb +34 -0
  410. data/lib/has_helpers/service_trust/bearer_token.rb +23 -0
  411. data/lib/has_helpers/service_trust/capability.rb +32 -0
  412. data/lib/has_helpers/service_trust/client/response.rb +13 -0
  413. data/lib/has_helpers/service_trust/client/retry.rb +35 -0
  414. data/lib/has_helpers/service_trust/client.rb +128 -0
  415. data/lib/has_helpers/service_trust/configuration.rb +76 -0
  416. data/lib/has_helpers/service_trust/engine.rb +10 -0
  417. data/lib/has_helpers/service_trust/errors.rb +32 -0
  418. data/lib/has_helpers/service_trust/graphql/authorizable.rb +72 -0
  419. data/lib/has_helpers/service_trust/graphql_client.rb +54 -0
  420. data/lib/has_helpers/service_trust/jwks_client.rb +133 -0
  421. data/lib/has_helpers/service_trust/jwt_codec.rb +45 -0
  422. data/lib/has_helpers/service_trust/key_manager.rb +160 -0
  423. data/lib/has_helpers/service_trust/rate_limiter.rb +27 -0
  424. data/lib/has_helpers/service_trust/replay_cache.rb +26 -0
  425. data/lib/has_helpers/service_trust/signer.rb +47 -0
  426. data/lib/has_helpers/service_trust/test.rb +60 -0
  427. data/lib/has_helpers/service_trust/verifier.rb +85 -0
  428. data/lib/has_helpers/service_trust/verify_middleware.rb +55 -0
  429. data/lib/has_helpers/service_trust.rb +51 -0
  430. data/lib/has_helpers/test_helpers/factory_data/unit_addresses.rb +42 -0
  431. data/lib/has_helpers/test_helpers/factory_data/unit_advisors.rb +272 -0
  432. data/lib/has_helpers/test_helpers/factory_data/unit_emails.rb +37 -0
  433. data/lib/has_helpers/test_helpers/factory_data/unit_organizations.rb +48 -0
  434. data/lib/has_helpers/test_helpers/factory_data/unit_people.rb +34 -0
  435. data/lib/has_helpers/test_helpers/factory_data/unit_phones.rb +41 -0
  436. data/lib/has_helpers/test_helpers/factory_data/unit_states.rb +26 -0
  437. data/lib/has_helpers/test_helpers/factory_data/unit_webs.rb +31 -0
  438. data/lib/has_helpers/test_helpers/shoulda_helpers.rb +249 -0
  439. data/lib/has_helpers/ui_attributes.rb +39 -0
  440. data/lib/has_helpers/utils/action.rb +87 -0
  441. data/lib/has_helpers/utils/active_record_utils.rb +40 -0
  442. data/lib/has_helpers/utils/alert.rb +42 -0
  443. data/lib/has_helpers/utils/container.rb +46 -0
  444. data/lib/has_helpers/utils/dataset.rb +34 -0
  445. data/lib/has_helpers/utils/email_notifier.rb +15 -0
  446. data/lib/has_helpers/utils/icon.rb +66 -0
  447. data/lib/has_helpers/utils/input.rb +45 -0
  448. data/lib/has_helpers/utils/log_notifier.rb +79 -0
  449. data/lib/has_helpers/utils/render_object.rb +32 -0
  450. data/lib/has_helpers/utils/s3_utils.rb +37 -0
  451. data/lib/has_helpers/utils/service.rb +87 -0
  452. data/lib/has_helpers/utils/slack_conf.rb +31 -0
  453. data/lib/has_helpers/utils/slack_notifier.rb +42 -0
  454. data/lib/has_helpers/utils/text_node.rb +34 -0
  455. data/lib/has_helpers/utils/user_restrictions.rb +266 -0
  456. data/lib/has_helpers/utils.rb +19 -0
  457. data/lib/has_helpers/version.rb +6 -0
  458. data/lib/has_helpers/web_socket_publisher.rb +20 -0
  459. data/lib/has_helpers/webhook.rb +17 -0
  460. data/lib/has_helpers.rb +63 -0
  461. data/lib/hq/resource.rb +55 -0
  462. data/lib/hq/structure/inserts.rb +176 -0
  463. data/lib/hq/structure.rb +115 -0
  464. data/lib/tasks/has_helpers/service_trust.rake +39 -0
  465. data/script/rails +10 -0
  466. data/sorbet/config +5 -0
  467. data/sorbet/rbi/gems/actionpack.rbi +1361 -0
  468. data/sorbet/rbi/gems/activemodel.rbi +755 -0
  469. data/sorbet/rbi/gems/activerecord.rbi +4713 -0
  470. data/sorbet/rbi/gems/activesupport.rbi +1970 -0
  471. data/sorbet/rbi/gems/barby.rbi +129 -0
  472. data/sorbet/rbi/gems/better_errors.rbi +155 -0
  473. data/sorbet/rbi/gems/binding_of_caller.rbi +22 -0
  474. data/sorbet/rbi/gems/byebug.rbi +19 -0
  475. data/sorbet/rbi/gems/chronic.rbi +480 -0
  476. data/sorbet/rbi/gems/chunky_png.rbi +510 -0
  477. data/sorbet/rbi/gems/climate_control.rbi +45 -0
  478. data/sorbet/rbi/gems/coderay.rbi +91 -0
  479. data/sorbet/rbi/gems/concurrent-ruby.rbi +1587 -0
  480. data/sorbet/rbi/gems/debug_inspector.rbi +18 -0
  481. data/sorbet/rbi/gems/erubi.rbi +26 -0
  482. data/sorbet/rbi/gems/holidays.rbi +362 -0
  483. data/sorbet/rbi/gems/i18n.rbi +132 -0
  484. data/sorbet/rbi/gems/method_source.rbi +63 -0
  485. data/sorbet/rbi/gems/mime-types-data.rbi +16 -0
  486. data/sorbet/rbi/gems/mime-types.rbi +217 -0
  487. data/sorbet/rbi/gems/mimemagic.rbi +36 -0
  488. data/sorbet/rbi/gems/minitest.rbi +282 -0
  489. data/sorbet/rbi/gems/mustache.rbi +151 -0
  490. data/sorbet/rbi/gems/paperclip.rbi +644 -0
  491. data/sorbet/rbi/gems/parallel.rbi +81 -0
  492. data/sorbet/rbi/gems/parallel_tests.rbi +27 -0
  493. data/sorbet/rbi/gems/pry-rails.rbi +71 -0
  494. data/sorbet/rbi/gems/pry.rbi +1964 -0
  495. data/sorbet/rbi/gems/rack.rbi +228 -0
  496. data/sorbet/rbi/gems/railties.rbi +364 -0
  497. data/sorbet/rbi/gems/rspec-core.rbi +1704 -0
  498. data/sorbet/rbi/gems/rspec-expectations.rbi +389 -0
  499. data/sorbet/rbi/gems/rspec-support.rbi +206 -0
  500. data/sorbet/rbi/gems/rspec.rbi +14 -0
  501. data/sorbet/rbi/gems/spreadsheet_architect.rbi +10 -0
  502. data/sorbet/rbi/gems/sprockets.rbi +659 -0
  503. data/sorbet/rbi/gems/terrapin.rbi +115 -0
  504. data/sorbet/rbi/gems/testhq.rbi +14 -0
  505. data/sorbet/rbi/gems/thor.rbi +482 -0
  506. data/sorbet/rbi/gems/thread_safe.rbi +81 -0
  507. data/sorbet/rbi/gems/tzinfo.rbi +407 -0
  508. data/sorbet/rbi/gems/zeitwerk.rbi +131 -0
  509. data/sorbet/rbi/hidden-definitions/errors.txt +35111 -0
  510. data/sorbet/rbi/hidden-definitions/hidden.rbi +59974 -0
  511. data/sorbet/rbi/sorbet-typed/lib/actionmailer/all/actionmailer.rbi +13 -0
  512. data/sorbet/rbi/sorbet-typed/lib/actionpack/all/actionpack.rbi +855 -0
  513. data/sorbet/rbi/sorbet-typed/lib/actionview/all/actionview.rbi +314 -0
  514. data/sorbet/rbi/sorbet-typed/lib/activemodel/all/activemodel.rbi +423 -0
  515. data/sorbet/rbi/sorbet-typed/lib/activerecord/>=5.2/activerecord.rbi +17 -0
  516. data/sorbet/rbi/sorbet-typed/lib/activerecord/all/activerecord.rbi +1415 -0
  517. data/sorbet/rbi/sorbet-typed/lib/activerecord/~>6.0.0/activerecord.rbi +475 -0
  518. data/sorbet/rbi/sorbet-typed/lib/activestorage/>=6.0.0/activestorage.rbi +19 -0
  519. data/sorbet/rbi/sorbet-typed/lib/activestorage/all/activestorage.rbi +173 -0
  520. data/sorbet/rbi/sorbet-typed/lib/activesupport/>=6.0.0.rc1/activesupport.rbi +23 -0
  521. data/sorbet/rbi/sorbet-typed/lib/activesupport/all/activesupport.rbi +625 -0
  522. data/sorbet/rbi/sorbet-typed/lib/bundler/all/bundler.rbi +8684 -0
  523. data/sorbet/rbi/sorbet-typed/lib/minitest/all/minitest.rbi +99 -0
  524. data/sorbet/rbi/sorbet-typed/lib/railties/all/railties.rbi +25 -0
  525. data/sorbet/rbi/sorbet-typed/lib/rainbow/all/rainbow.rbi +254 -0
  526. data/sorbet/rbi/sorbet-typed/lib/ruby/all/gem.rbi +4222 -0
  527. data/sorbet/rbi/sorbet-typed/lib/ruby/all/open3.rbi +111 -0
  528. data/sorbet/rbi/sorbet-typed/lib/ruby/all/resolv.rbi +543 -0
  529. data/sorbet/rbi/todo.rbi +244 -0
  530. data/spec/commands/cards/available_cards_command_spec.rb +90 -0
  531. data/spec/commands/cards/card_data_command_spec.rb +1048 -0
  532. data/spec/constructors/has_helpers/base_constructor_spec.rb +49 -0
  533. data/spec/controllers/has_helpers/controller_spec.rb +71 -0
  534. data/spec/controllers/has_helpers/xlsx_export_spec.rb +96 -0
  535. data/spec/factories/advisors.rb +20 -0
  536. data/spec/factories/anonymous_cowards.rb +5 -0
  537. data/spec/factories/applications.rb +5 -0
  538. data/spec/factories/appointments.rb +6 -0
  539. data/spec/factories/attachment.rb +15 -0
  540. data/spec/factories/audits.rb +5 -0
  541. data/spec/factories/basic_schedules.rb +12 -0
  542. data/spec/factories/cards/card_definition_resources.rb +7 -0
  543. data/spec/factories/cards/card_definitions.rb +11 -0
  544. data/spec/factories/cards/card_fields.rb +22 -0
  545. data/spec/factories/cards/role_card_fields.rb +17 -0
  546. data/spec/factories/cards/role_resource_cards.rb +12 -0
  547. data/spec/factories/constant_definition.rb +7 -0
  548. data/spec/factories/contracts.rb +10 -0
  549. data/spec/factories/data_migration_models.rb +9 -0
  550. data/spec/factories/demographics.rb +22 -0
  551. data/spec/factories/events.rb +21 -0
  552. data/spec/factories/firms.rb +21 -0
  553. data/spec/factories/imports.rb +9 -0
  554. data/spec/factories/meetups.rb +13 -0
  555. data/spec/factories/organizations.rb +13 -0
  556. data/spec/factories/policies.rb +7 -0
  557. data/spec/factories/policy_notes.rb +9 -0
  558. data/spec/factories/read_only_values.rb +7 -0
  559. data/spec/factories/resources.rb +7 -0
  560. data/spec/factories/roles.rb +11 -0
  561. data/spec/factories/schedules.rb +13 -0
  562. data/spec/factories/shared/flattener/attributes.rb +10 -0
  563. data/spec/factories/shared/flattener/columns.rb +6 -0
  564. data/spec/factories/shared/flattener/resources.rb +5 -0
  565. data/spec/factories/templates.rb +12 -0
  566. data/spec/factories/user_organizations.rb +23 -0
  567. data/spec/factories/user_session.rb +11 -0
  568. data/spec/factories/users.rb +55 -0
  569. data/spec/fixtures/sample_config.yml +3 -0
  570. data/spec/graphql/has_helpers/mutations/external_data_sync_spec.rb +101 -0
  571. data/spec/graphql/has_helpers/resolvers/cards/available_cards_spec.rb +36 -0
  572. data/spec/graphql/has_helpers/resolvers/cards/card_data_spec.rb +42 -0
  573. data/spec/graphql/has_helpers/resolvers/cards/hover_card_data_spec.rb +59 -0
  574. data/spec/has_helpers/cards/cache_spec.rb +58 -0
  575. data/spec/has_helpers/cards/config_spec.rb +68 -0
  576. data/spec/has_helpers/cards/orphan_card_sweeper_spec.rb +97 -0
  577. data/spec/has_helpers/cards/remote_role_spec.rb +17 -0
  578. data/spec/has_helpers/cards/remote_roles_spec.rb +97 -0
  579. data/spec/has_helpers/cards/roles_spec.rb +52 -0
  580. data/spec/has_helpers/cards/smoke_spec.rb +168 -0
  581. data/spec/has_helpers/cards/support.rb +52 -0
  582. data/spec/has_helpers/resolvers/internal/roles_spec.rb +73 -0
  583. data/spec/has_helpers/service_trust/audit_log_cleanup_worker_spec.rb +33 -0
  584. data/spec/has_helpers/service_trust/audit_spec.rb +70 -0
  585. data/spec/has_helpers/service_trust/authenticated_as_service_spec.rb +65 -0
  586. data/spec/has_helpers/service_trust/authorization_spec.rb +37 -0
  587. data/spec/has_helpers/service_trust/capability_spec.rb +35 -0
  588. data/spec/has_helpers/service_trust/client_spec.rb +129 -0
  589. data/spec/has_helpers/service_trust/configuration_spec.rb +35 -0
  590. data/spec/has_helpers/service_trust/engine_spec.rb +13 -0
  591. data/spec/has_helpers/service_trust/errors_spec.rb +23 -0
  592. data/spec/has_helpers/service_trust/graphql_authentication_spec.rb +59 -0
  593. data/spec/has_helpers/service_trust/graphql_authorizable_spec.rb +91 -0
  594. data/spec/has_helpers/service_trust/graphql_client_spec.rb +47 -0
  595. data/spec/has_helpers/service_trust/jwks_client_spec.rb +85 -0
  596. data/spec/has_helpers/service_trust/jwks_controller_spec.rb +47 -0
  597. data/spec/has_helpers/service_trust/jwt_codec_spec.rb +30 -0
  598. data/spec/has_helpers/service_trust/key_manager_spec.rb +80 -0
  599. data/spec/has_helpers/service_trust/key_spec.rb +52 -0
  600. data/spec/has_helpers/service_trust/rake_tasks_spec.rb +58 -0
  601. data/spec/has_helpers/service_trust/rate_limiter_spec.rb +31 -0
  602. data/spec/has_helpers/service_trust/replay_cache_spec.rb +29 -0
  603. data/spec/has_helpers/service_trust/signer_verifier_spec.rb +159 -0
  604. data/spec/has_helpers/service_trust/smoke_spec.rb +216 -0
  605. data/spec/has_helpers/service_trust/support.rb +121 -0
  606. data/spec/has_helpers/service_trust/test_module_spec.rb +39 -0
  607. data/spec/has_helpers/service_trust/verify_middleware_spec.rb +82 -0
  608. data/spec/helpers/application_spec.rb +155 -0
  609. data/spec/importers/cards/importer_spec.rb +861 -0
  610. data/spec/internal/app/assets/config/manifest.js +0 -0
  611. data/spec/internal/app/constants/cat_egory.rb +33 -0
  612. data/spec/internal/app/constants/color.rb +19 -0
  613. data/spec/internal/app/constants/exclude_group_nil_constant.rb +5 -0
  614. data/spec/internal/app/constants/foo.rb +6 -0
  615. data/spec/internal/app/constants/fruit.rb +3 -0
  616. data/spec/internal/app/constants/generic_group_example.rb +12 -0
  617. data/spec/internal/app/constants/my_constant.rb +4 -0
  618. data/spec/internal/app/constants/product.rb +3 -0
  619. data/spec/internal/app/constants/shirt_size.rb +18 -0
  620. data/spec/internal/app/constants/simple_option.rb +14 -0
  621. data/spec/internal/app/constants/status.rb +5 -0
  622. data/spec/internal/app/constructors/organization_constructor.rb +4 -0
  623. data/spec/internal/app/controllers/application_controller.rb +27 -0
  624. data/spec/internal/app/controllers/search_extensions.rb +3 -0
  625. data/spec/internal/app/indices/advisor_index.rb +35 -0
  626. data/spec/internal/app/indices/contract_index.rb +52 -0
  627. data/spec/internal/app/indices/firm_index.rb +16 -0
  628. data/spec/internal/app/indices/policy_index.rb +20 -0
  629. data/spec/internal/app/models/advisor.rb +24 -0
  630. data/spec/internal/app/models/agency.rb +3 -0
  631. data/spec/internal/app/models/anonymous_coward.rb +5 -0
  632. data/spec/internal/app/models/another_event.rb +2 -0
  633. data/spec/internal/app/models/appointment.rb +3 -0
  634. data/spec/internal/app/models/attachment.rb +10 -0
  635. data/spec/internal/app/models/basic_schedule.rb +6 -0
  636. data/spec/internal/app/models/beneficiary.rb +6 -0
  637. data/spec/internal/app/models/client.rb +5 -0
  638. data/spec/internal/app/models/contract.rb +9 -0
  639. data/spec/internal/app/models/demographic.rb +4 -0
  640. data/spec/internal/app/models/division.rb +3 -0
  641. data/spec/internal/app/models/event.rb +10 -0
  642. data/spec/internal/app/models/event_template.rb +1 -0
  643. data/spec/internal/app/models/firm.rb +18 -0
  644. data/spec/internal/app/models/has_audits/audit.rb +15 -0
  645. data/spec/internal/app/models/import.rb +7 -0
  646. data/spec/internal/app/models/meetup.rb +8 -0
  647. data/spec/internal/app/models/organization.rb +17 -0
  648. data/spec/internal/app/models/organization_event.rb +5 -0
  649. data/spec/internal/app/models/policy.rb +16 -0
  650. data/spec/internal/app/models/policy_holder.rb +7 -0
  651. data/spec/internal/app/models/policy_note.rb +8 -0
  652. data/spec/internal/app/models/project.rb +3 -0
  653. data/spec/internal/app/models/read_only_value.rb +8 -0
  654. data/spec/internal/app/models/schedule.rb +12 -0
  655. data/spec/internal/app/models/template.rb +9 -0
  656. data/spec/internal/app/models/user.rb +31 -0
  657. data/spec/internal/app/models/user_organization.rb +17 -0
  658. data/spec/internal/app/views/has_lists/lists/_table.html.erb +1 -0
  659. data/spec/internal/config/database-standard.yml +24 -0
  660. data/spec/internal/config/initializers/resources.rb +19 -0
  661. data/spec/internal/config/routes.rb +19 -0
  662. data/spec/internal/config/storage.yml +3 -0
  663. data/spec/internal/db/good_job_schema.rb +141 -0
  664. data/spec/internal/db/schema.rb +334 -0
  665. data/spec/internal/log/.gitignore +1 -0
  666. data/spec/internal/public/favicon.ico +0 -0
  667. data/spec/lib/flattener/base_spec.rb +93 -0
  668. data/spec/lib/flattener/column/types_spec.rb +65 -0
  669. data/spec/lib/has_helpers/active_record/base_spec.rb +53 -0
  670. data/spec/lib/has_helpers/active_record/date_range_spec.rb +498 -0
  671. data/spec/lib/has_helpers/active_record/lookup_model_spec.rb +408 -0
  672. data/spec/lib/has_helpers/active_record/nested_resource_spec.rb +43 -0
  673. data/spec/lib/has_helpers/app/command/reports/generator_specs.rb +887 -0
  674. data/spec/lib/has_helpers/app/command_spec.rb +117 -0
  675. data/spec/lib/has_helpers/app/support/mocks/graphql_reports_helpers.rb +96 -0
  676. data/spec/lib/has_helpers/app/support/mocks/reports_api_mock.rb +47 -0
  677. data/spec/lib/has_helpers/attributes_spec.rb +220 -0
  678. data/spec/lib/has_helpers/aws_spec.rb +126 -0
  679. data/spec/lib/has_helpers/concerns/hydrate_concern_spec.rb +473 -0
  680. data/spec/lib/has_helpers/constant/definition_spec.rb +128 -0
  681. data/spec/lib/has_helpers/constant_spec.rb +432 -0
  682. data/spec/lib/has_helpers/core_ext/array_spec.rb +22 -0
  683. data/spec/lib/has_helpers/core_ext/date/infinity_spec.rb +67 -0
  684. data/spec/lib/has_helpers/core_ext/date_spec.rb +27 -0
  685. data/spec/lib/has_helpers/core_ext/false_class_spec.rb +7 -0
  686. data/spec/lib/has_helpers/core_ext/float_spec.rb +9 -0
  687. data/spec/lib/has_helpers/core_ext/hash_spec.rb +48 -0
  688. data/spec/lib/has_helpers/core_ext/integer_spec.rb +9 -0
  689. data/spec/lib/has_helpers/core_ext/nil_class_spec.rb +17 -0
  690. data/spec/lib/has_helpers/core_ext/object_spec.rb +54 -0
  691. data/spec/lib/has_helpers/core_ext/proc_spec.rb +24 -0
  692. data/spec/lib/has_helpers/core_ext/string_spec.rb +154 -0
  693. data/spec/lib/has_helpers/core_ext/true_class_spec.rb +7 -0
  694. data/spec/lib/has_helpers/data_migration/base_spec.rb +470 -0
  695. data/spec/lib/has_helpers/data_migration/large_worker_spec.rb +34 -0
  696. data/spec/lib/has_helpers/data_migration/master_worker_spec.rb +51 -0
  697. data/spec/lib/has_helpers/data_migration/migrator_spec.rb +113 -0
  698. data/spec/lib/has_helpers/data_migration/model_spec.rb +52 -0
  699. data/spec/lib/has_helpers/data_migration/small_worker_spec.rb +34 -0
  700. data/spec/lib/has_helpers/good_job/notification_alert_cleanup_worker_spec.rb +89 -0
  701. data/spec/lib/has_helpers/hash_accessors_spec.rb +66 -0
  702. data/spec/lib/has_helpers/index_spec.rb +442 -0
  703. data/spec/lib/has_helpers/lazy_config_spec.rb +31 -0
  704. data/spec/lib/has_helpers/organization_merge_spec.rb +88 -0
  705. data/spec/lib/has_helpers/resource_importer_spec.rb +119 -0
  706. data/spec/lib/has_helpers/rspec_spec.rb +16 -0
  707. data/spec/lib/has_helpers/search/provider_spec.rb +26 -0
  708. data/spec/lib/has_helpers/search/search_utils_spec.rb +134 -0
  709. data/spec/lib/has_helpers/sentry/graphql_trace_spec.rb +140 -0
  710. data/spec/lib/has_helpers/ui_attributes_spec.rb +42 -0
  711. data/spec/lib/has_helpers/utils/action_spec.rb +49 -0
  712. data/spec/lib/has_helpers/utils/alert_spec.rb +33 -0
  713. data/spec/lib/has_helpers/utils/dataset_spec.rb +10 -0
  714. data/spec/lib/has_helpers/utils/icon_spec.rb +23 -0
  715. data/spec/lib/has_helpers/utils/input_spec.rb +40 -0
  716. data/spec/lib/has_helpers/utils/log_notifier_spec.rb +25 -0
  717. data/spec/lib/has_helpers/utils/render_object_spec.rb +17 -0
  718. data/spec/lib/has_helpers/utils/s3_utils_spec.rb +78 -0
  719. data/spec/lib/has_helpers/utils/service_spec.rb +79 -0
  720. data/spec/lib/has_helpers/utils/slack_notifier_spec.rb +70 -0
  721. data/spec/lib/has_helpers/utils/user_restrictions_spec.rb +343 -0
  722. data/spec/lib/hq/structure/inserts_spec.rb +152 -0
  723. data/spec/lib/hq/structure_spec.rb +12 -0
  724. data/spec/models/cards/base_spec.rb +149 -0
  725. data/spec/models/cards/card_definition_resource_spec.rb +103 -0
  726. data/spec/models/cards/card_definition_spec.rb +50 -0
  727. data/spec/models/cards/card_field_spec.rb +46 -0
  728. data/spec/models/cards/field_builder_spec.rb +318 -0
  729. data/spec/models/cards/registry_spec.rb +147 -0
  730. data/spec/models/cards/role_card_field_spec.rb +111 -0
  731. data/spec/models/cards/role_resource_card_spec.rb +112 -0
  732. data/spec/models/has_helpers/master_record_spec.rb +20 -0
  733. data/spec/models/has_helpers/user_organization_spec.rb +28 -0
  734. data/spec/models/has_helpers/user_session_spec.rb +16 -0
  735. data/spec/presenters/base_presenter_spec.rb +166 -0
  736. data/spec/rails_helper.rb +119 -0
  737. data/spec/requests/has_helpers/factory_controller_spec.rb +159 -0
  738. data/spec/requests/has_helpers/health_check_controller_spec.rb +37 -0
  739. data/spec/scenarios/empty_setup.rb +6 -0
  740. data/spec/spec_helper.rb +22 -0
  741. data/spec/support/has_helpers/database_helpers.rb +12 -0
  742. data/spec/utils/sync_utils_spec.rb +233 -0
  743. data/spec/workers/goodjob/elasticsearch_process_batch_spec.rb +94 -0
  744. data/spec/workers/goodjob/elasticsearch_process_change_logs_master_worker_spec.rb +40 -0
  745. data/spec/workers/goodjob/elasticsearch_process_change_logs_worker_spec.rb +82 -0
  746. data/spec/workers/goodjob/elasticsearch_process_queue_spec.rb +89 -0
  747. data/spec/workers/goodjob/elasticsearch_reindex_spec.rb +49 -0
  748. metadata +1593 -0
@@ -0,0 +1,428 @@
1
+ # typed: false
2
+ # frozen_string_literal: true
3
+
4
+ module Flattener
5
+ class Base
6
+ include ActiveModel::Model
7
+ attr_writer :include_for # TODO: better name; Used to determine what associations to include, defined in initializer
8
+ # e.g. :dw, :api, :importers
9
+
10
+ EXCLUDE_COLUMNS = ["delta", "express_add", "photo_updated_at", "photo_file_name", "photo_content_type", "photo_file_size"]
11
+ CONSTANT_COLUMNS = ["abbreviation", "id", "name"]
12
+
13
+ def associations
14
+ @associations ||= self.class.associations.select { |a| (a.for.to_a & include_for).present? }.map(&:dup)
15
+ end
16
+
17
+ def add(*names, **options, &block)
18
+ names.each do |name|
19
+ association = associations.detect { |a| a.ar_association.name == name }
20
+ associations << association = self.class.find_association(name).dup unless association
21
+ association.update(**options, for: Flattener::Base.default_for.keys)
22
+ association.flattener = Flattener.for(association.klass).new.tap { |f| f.instance_eval(&block) } if block.present?
23
+ end
24
+ end
25
+
26
+ # HACK Does this really belong inside of the flattener?
27
+ def add_column(name, sql_type, **options)
28
+ sql_column = Flattener::Column.build_sql_column(name, sql_type)
29
+ model_columns << Flattener::Column.new(sql_column: sql_column, klass: self.class.model_klass, association: self.class.find_column_association(sql_column.name), **options)
30
+ end
31
+
32
+ def remove(*args)
33
+ args.each do |name|
34
+ association = associations.detect { |a| a.ar_association_name == name }
35
+ associations.delete(association) if association
36
+ end
37
+ end
38
+
39
+ def join(name, **options, &block)
40
+ if add(name, always_include: true, merge_into_parent: true, **options, &block)
41
+ # rubocop:disable Rails/SkipsModelValidations
42
+ update_column(name.to_s.foreign_key, alias_name: :id)
43
+ update_column(self.class.model_klass.primary_key, alias_name: :link_id)
44
+ # rubocop:enable Rails/SkipsModelValidations
45
+ end
46
+ end
47
+
48
+ def columns
49
+ @columns ||= model_columns.
50
+ lazy.
51
+ select { |c| include_columns.blank? || include_columns.include?(c.name.to_sym) }.
52
+ reject { |c| exclude_columns.include?(c.name.to_sym) } # FIXME: It seems inconsistent to return string column names (from .columns) yet require symbol names for excluded columns. *
53
+ end
54
+
55
+ def include_for
56
+ @include_for ||= Flattener::Base.default_for.keys
57
+ end
58
+
59
+ def include_columns(*column_names)
60
+ (@include_columns ||= []).tap do |include_columns|
61
+ include_columns.concat column_names if column_names
62
+ end
63
+ end
64
+
65
+ def exclude_columns(*column_names)
66
+ (@exclude_columns ||= []).tap do |exclude_columns|
67
+ exclude_columns.concat column_names if column_names
68
+ end
69
+ end
70
+
71
+ def update_column(name, **options)
72
+ raise ArgumentError, "nil is not a valid column" if name.nil?
73
+ begin
74
+ columns.detect { |column| column.name.to_s == name.to_s }.update(options)
75
+ rescue NoMethodError
76
+ raise "No column `#{name}` on #{self.class.name}" if ::HasUtils::Env.development?
77
+ end
78
+ end
79
+
80
+ def flatten(depth = 1)
81
+ Flattener::Resource.new(flattener: self).flatten(depth)
82
+ end
83
+
84
+ def belongs_to(*args)
85
+ singleton_class.belongs_to(*args)
86
+ end
87
+
88
+ private
89
+
90
+ def model_columns
91
+ @model_columns ||= self.class.columns.to_a.
92
+ map(&:dup)
93
+ end
94
+
95
+ class << self
96
+ attr_writer :associations, :columns, :model_klass, :default_for
97
+ attr_accessor :is_pivot
98
+ attr_reader :pivot_columns, :pivot_associations, :pivot_count
99
+
100
+ def model_klass
101
+ @model_klass ||= begin
102
+ klass = singleton_class? ? superclass : self # Singleton classes are not named, but in this case we really want the name of its superclass.
103
+ klass.name.gsub("Flatteners::", "").constantize
104
+ end
105
+ end
106
+
107
+ def associations
108
+ model_associations.reject(&:removed)
109
+ end
110
+
111
+ # HACK Does this really belong inside of the flattener?
112
+ def belongs_to(name, scope = nil, **options)
113
+ ar_association = ::ActiveRecord::Associations::Builder::BelongsTo.create_reflection(model_klass, name, scope, options)
114
+ Flattener::Association.new(ar_association).tap do |association|
115
+ model_associations << association
116
+ add_column ar_association.foreign_key, "integer"
117
+ end
118
+ end
119
+
120
+ # HACK Does this really belong inside of the flattener?
121
+ def has_many(name, scope = nil, **options)
122
+ ar_association = ::ActiveRecord::Associations::Builder::HasMany.create_reflection(model_klass, name, scope, options)
123
+ Flattener::Association.new(ar_association).tap do |association|
124
+ model_associations << association
125
+ end
126
+ end
127
+
128
+ # Defines the pivot for the current flattener.
129
+ # HACK: See notes for belongs_to *
130
+ #=== Options
131
+ # +:count+ The number of pivots (sets of fields) to generate. Defaults to 1.
132
+ # +:expand_on+ Collection or proc returning the names on which to expand. A proc may accept one argument which is the class of the parent flattener.
133
+ # +:is_lookup+ Set this to true if the pivot table requires a look-up.
134
+ #=== Examples
135
+ # pivot!(count: 3)
136
+ # pivot!(count: 5, expand_on: %w[home office other])
137
+ # pivot!(
138
+ # count: 5,
139
+ # expand_on: -> (klass) { HasContactInfo::AddressLocation.scoped_to(klass)::ALL.map(&:name) }
140
+ # )
141
+ # pivot!(count: 5, is_lookup: true)
142
+ def pivot!(
143
+ count: 1,
144
+ expand_on: nil,
145
+ expand_over: [],
146
+ excluded: [/id\z/, "created_at"],
147
+ included: [], # Overrides excluded values
148
+ is_lookup: nil,
149
+ &block
150
+ )
151
+ self.is_pivot = true
152
+ @pivot_count = count
153
+ @pivot_expand_on = expand_on
154
+ @pivot_is_lookup = is_lookup
155
+
156
+ if block
157
+ # FIXME: This is too hacky. We need to find an alternative way to define pivots *
158
+ # Get a clone of the current flattener for use in evaluating the
159
+ # given block. This allow the pivot associations and columns to be
160
+ # determined while preserving the columns and associtions on the
161
+ # non-pivoted flattener.
162
+ instance = clone
163
+ if @model_associations.present?
164
+ instance.instance_eval do
165
+ @model_associations = @model_associations.map(&:clone)
166
+ end
167
+ end
168
+ instance.model_klass = model_klass # model_klass may otherwise fail since the instance class has no name.
169
+ else
170
+ instance = self
171
+ end
172
+
173
+ ## Determine which associations to use for the pivot
174
+ # Remove associations except for constants (default behavior, perhaps there is something better)
175
+ instance.remove(
176
+ *(associations.reject(&:is_constant?)).map(&:name)
177
+ )
178
+ # Eval the block after removing associations so that changes
179
+ # made in the block remain in effect.
180
+ instance.instance_eval(&block) if block
181
+ @pivot_associations = instance.associations.dup
182
+
183
+ # Determine the pivot columns
184
+ @pivot_columns = instance.columns.lazy.
185
+ reject { |col| excluded.any? { |ex| ex.match(col.name) } }.
186
+ to_a # Must force to an array to avoid an infinite loop when adding columns and otherwise incorrect results.
187
+
188
+ # Remove columns
189
+ exclude_columns(
190
+ *@pivot_columns.lazy.
191
+ reject do |col|
192
+ # Do not exclude any columns which have been explicitly included.
193
+ included.any? { |inc| inc.match(col.name) }
194
+ end.
195
+ map(&:name).
196
+ map(&:to_sym)
197
+ )
198
+
199
+ @pivot_columns.concat(
200
+ instance.send(:model_columns).
201
+ select do |col|
202
+ included.any? { |inc| inc.match(col.name) }
203
+ end.
204
+ to_a
205
+ )
206
+
207
+ # Add expansions
208
+ expansions = Array(expand_over)
209
+ expansions << nil if expansions.none? # Add a blank entry which provides a value to iterate over.
210
+
211
+ expansions.each do |expansion|
212
+ expansion = "#{ expansion.underscore }_" if expansion
213
+ (1..count).each do |idx|
214
+ # add expanded columns
215
+ @pivot_columns.each do |col|
216
+ add_column("#{ expansion }#{ col.name.to_sym }_#{ idx }", col.sql_type)
217
+ end
218
+
219
+ # add expanded associations
220
+ @pivot_associations.each do |assoc|
221
+ add_column("#{ expansion }#{ assoc.foreign_key }_#{ idx }", "integer")
222
+ add_column("#{ expansion }#{ assoc.foreign_key.to_s.remove(/_id\z/) }_#{ idx }", "character varying")
223
+ end
224
+ end
225
+ end
226
+ end
227
+
228
+ # If the current flattener is a pivot then this method returns a class
229
+ # which generates only the pivots attributes and associations. Otherwise,
230
+ # it returns the current flattener class.
231
+ def pivot_or_self
232
+ if is_pivot
233
+ # Return a duplicate of the current flattener class with the
234
+ # pivot additions mixed in.
235
+ self.dup.tap do |klass|
236
+ klass.prepend(::Flattener::Pivot)
237
+ klass.is_lookup = @pivot_is_lookup
238
+ end
239
+ else
240
+ self
241
+ end
242
+ end
243
+
244
+ alias is_pivot? is_pivot
245
+
246
+ def pivot_expand_on(klass = nil)
247
+ if @pivot_expand_on.respond_to?(:call)
248
+ @pivot_expand_on.call(*klass)
249
+ else
250
+ Array(@pivot_expand_on)
251
+ end
252
+ end
253
+
254
+ def columns
255
+ @columns ||= model_columns.
256
+ lazy.
257
+ select { |sql_column| include_column?(sql_column) }
258
+ end
259
+
260
+ def exclude_columns(*column_names)
261
+ (@exclude_columns ||= []).tap do |exclude_columns|
262
+ exclude_columns.concat column_names if column_names
263
+ end
264
+ end
265
+
266
+ def default_for
267
+ @default_for ||= {}
268
+ end
269
+
270
+ def include_column?(column)
271
+ EXCLUDE_COLUMNS.exclude?(column.name) && exclude_columns.exclude?(column.name.to_sym) && (!is_constant?(model_klass) || bypass_constant_column_removals? || CONSTANT_COLUMNS.include?(column.name))
272
+ end
273
+
274
+ # When including HasHelpers::Constant to a class this removes all columns
275
+ # that are not from the constants relation. This will bypass that validation if true including other columns
276
+ def bypass_constant_column_removals?(bypass = false)
277
+ @bypass_constant_column_removals ||= bypass
278
+ end
279
+
280
+ def is_constant?(klass = model_klass)
281
+ klass.include?(HasHelpers::Constant)
282
+ end
283
+
284
+ def model_associations
285
+ @model_associations ||= model_klass.reflect_on_all_associations.map { |a| Flattener::Association.new(a) }
286
+ end
287
+
288
+ def model_columns
289
+ @model_columns ||= begin
290
+ # Flatteners are loaded before running migrations
291
+ # Only load columns if the table exists
292
+ connection = ::ActiveRecord::Base.connection
293
+ table_name = model_klass.table_name
294
+
295
+ model_klass_columns =
296
+ if connection.table_exists?(table_name) || connection.view_exists?(table_name)
297
+ model_klass.columns
298
+ else
299
+ []
300
+ end
301
+ model_klass_columns.map { |sql_column| Flattener::Column.new(sql_column: sql_column, klass: model_klass, association: find_column_association(sql_column.name)) }
302
+ end
303
+ end
304
+ end
305
+
306
+ # rubocop:disable Lint/IneffectiveAccessModifier
307
+ def self.flatten(trait: nil)
308
+ new.tap do |flattener|
309
+ flattener.instance_eval(&traits[trait]) if traits[trait]
310
+ end.flatten
311
+ end
312
+
313
+ # Add/update an association to to be included when using a flattener.
314
+ #
315
+ # Examples
316
+ #
317
+ # Flattener.for(Advisor).add :bank_accounts, for: [:dw, api]
318
+ # the 'for:' argument will default to all of the options available
319
+ #
320
+ # class Flatteners::Advisor < Flattener::Base
321
+ # add :errors_and_omissions
322
+ # end
323
+ #
324
+ def self.add(*args, **options, &block)
325
+ args.each do |name|
326
+ if (association = find_association(name))
327
+ association.update(**options, for: ::Flattener::Base.default_for.lazy.map { |k| k.first }, removed: false)
328
+ association.flattener = Flattener.for(association.klass).new.tap { |f| f.instance_eval(&block) } if block.present?
329
+ end
330
+ end
331
+ end
332
+
333
+ # Add a custom column to the flattener
334
+ #
335
+ # Example
336
+ #
337
+ # Flattener.for(Advisor).add_column :amount, "numeric(20,5)"
338
+ # Flattener.for(Advisor).add_column :name, "text"
339
+ #
340
+ # HACK Does this really belong inside of the flattener?
341
+ def self.add_column(name, sql_type, **options)
342
+ sql_column = Flattener::Column.build_sql_column(name, sql_type)
343
+ model_columns << Flattener::Column.new(sql_column: sql_column, klass: model_klass, association: find_column_association(sql_column.name), **options)
344
+ end
345
+
346
+ # Remove a association(s) from use by the flattener
347
+ #
348
+ # Example
349
+ #
350
+ # Flattener.for(Advisor).remove :bank_accounts, :errors_and_omissions
351
+ #
352
+ def self.remove(*args)
353
+ args.each do |name|
354
+ if (association = find_association(name))
355
+ association.remove
356
+ end
357
+ end
358
+ end
359
+
360
+ # Adds/updates an association, usually on a join table, aliases the foreign
361
+ # key of the added association to :id, and aliases the primary key to :link_id.
362
+ #
363
+ # This is for the benefit of the end user not having to understand the database
364
+ # relationships
365
+ #
366
+ # Example
367
+ #
368
+ # class Flatteners::BankAccountOwner < Flattener::Base
369
+ # alias :bank_account
370
+ # join :bank_account
371
+ # end
372
+ #
373
+ # # In this example, the end result of flattening will produce:
374
+ # # - :bank_account_owner aliased to :bank_account
375
+ # # - an attribute `bank_account_id`, which is the bank_account_owner's :bank_account_id
376
+ # # - an attribue `bank_account_link_id`, which is the bank_account_owner's :id
377
+ #
378
+ def self.join(name, **options, &block)
379
+ if add(name, always_include: true, merge_into_parent: true, **options, &block)
380
+ # rubocop:disable Rails/SkipsModelValidations
381
+ update_column(name.to_s.foreign_key, alias_name: :id)
382
+ update_column(model_klass.primary_key, alias_name: :link_id)
383
+ # rubocop:enable Rails/SkipsModelValidations
384
+ end
385
+ end
386
+
387
+ # Traits allow you to name groups of flattener settings inside of a block.
388
+ #
389
+ # Example
390
+ #
391
+ # class Flatteners::Advisor < Flattener::Base
392
+ # trait :dw do
393
+ # exclude_columns :organization_id
394
+ # remove :adjustments
395
+ # end
396
+ # end
397
+ #
398
+ # Flattener.for(Advisor).flatten(trait: :dw)
399
+ #
400
+ def self.trait(trait_name, &block)
401
+ traits[trait_name] = block
402
+ end
403
+
404
+ def self.update_column(name, **options)
405
+ raise ArgumentError, "nil is not a valid column" if name.nil?
406
+ begin
407
+ columns.detect { |column| column.name.to_s == name.to_s }.update(options)
408
+ rescue NoMethodError
409
+ raise "No column `#{name}` on #{self.name}" if ::HasUtils::Env.development?
410
+ end
411
+ end
412
+
413
+ def self.find_association(name)
414
+ model_associations.detect { |a| a.ar_association_name == name }
415
+ end
416
+
417
+ def self.find_column_association(foreign_key)
418
+ model_associations.detect { |a| a.macro != :has_many && a.ar_association.foreign_key.to_s == foreign_key.to_s }
419
+ end
420
+
421
+ def self.traits
422
+ @traits ||= {}
423
+ end
424
+
425
+ private_class_method :traits
426
+ # rubocop:enable Lint/IneffectiveAccessModifier
427
+ end
428
+ end
@@ -0,0 +1,71 @@
1
+ # typed: false
2
+ # frozen_string_literal: true
3
+
4
+ module Flattener
5
+ class Column
6
+ # FIXME: This can probably be moved elsewhere *
7
+ module Types
8
+ # Normalized data types
9
+ ALL = [
10
+ STRING = :string,
11
+ CURRENCY = :currency,
12
+ PERCENT = :percent,
13
+ DATE = :date,
14
+ DATETIME = :datetime,
15
+ BOOLEAN = :boolean,
16
+ TEXT = :text,
17
+ INTEGER = :integer,
18
+ DECIMAL = :decimal,
19
+ FLOAT = :float,
20
+ JSON = :json,
21
+ ARRAY = :array,
22
+ UUID = :uuid,
23
+ INTERVAL = :interval
24
+ ]
25
+
26
+ #=== Params
27
+ # +raw_sql_type+: The description of the SQL type, e.g. "boolean", "date", "character varying(63)", "timestamp without time zone"
28
+ def self.sql_type_to_type(raw_sql_type)
29
+ sql_type = raw_sql_type.to_s.downcase # Some mild normalization to allow a broader domain range.
30
+ type =
31
+ if sql_type =~ /character varying/ || sql_type == "string" || sql_type == "import_key"
32
+ STRING
33
+ elsif /\A(?:numeric|decimal)(?:\((?<precision>\d+),(?<scale>\d+)\))?\z/ =~ sql_type
34
+ # FIXME: This handles numeric (no scale and precision) to work around the current state of the DW. This should be removed once all numeric columns have scale and precision set. *
35
+ # elsif /\A(numeric|decimal)\((?<precision>\d+),(?<scale>\d+)\)\z/ =~ sql_type
36
+ if (precision == "7" && scale == "2") || # App small $
37
+ (precision == "12" && scale == "2") || # App large $
38
+ (precision == "20" && scale == "5") # DW $
39
+ CURRENCY
40
+ elsif precision == "6" && scale == "3" || precision == "9" && scale == "6"
41
+ PERCENT
42
+ else
43
+ DECIMAL
44
+ end
45
+ elsif sql_type == "date"
46
+ DATE
47
+ elsif sql_type =~ /timestamp/ || sql_type == "datetime" || sql_type == "past_or_present_date"
48
+ DATETIME
49
+ elsif sql_type == "boolean" || sql_type == "bi_state_boolean"
50
+ BOOLEAN
51
+ elsif sql_type == "text"
52
+ TEXT
53
+ elsif sql_type == "integer" || sql_type == "bigint"
54
+ INTEGER
55
+ elsif sql_type == "double precision"
56
+ FLOAT
57
+ elsif sql_type == "json" || sql_type == "jsonb"
58
+ JSON
59
+ elsif sql_type == "array"
60
+ ARRAY
61
+ elsif sql_type == "uuid"
62
+ UUID
63
+ elsif sql_type == "interval"
64
+ INTERVAL
65
+ end
66
+
67
+ type || raise(::ArgumentError, "No type matches SQL type '#{ raw_sql_type }'")
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,97 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ require "has_helpers/flattener/column/types"
5
+
6
+ module Flattener
7
+ class Column
8
+ include ActiveModel::Model
9
+
10
+ attr_accessor :association, :sql_column, :klass, :alias_name, :reject_if
11
+ attr_writer :type, :expand_on, :partial_type_id
12
+ TIMESTAMP_COLUMNS = ["created_at", "updated_at"]
13
+
14
+ delegate :sql_type, :name, to: :sql_column
15
+
16
+ def update(args)
17
+ args.each do |k, v|
18
+ send("#{k}=", v)
19
+ end
20
+ end
21
+
22
+ delegate :table_name, to: :klass
23
+
24
+ def is_primary_key?
25
+ klass.primary_key == name
26
+ end
27
+
28
+ def is_timestamp?
29
+ TIMESTAMP_COLUMNS.include? name
30
+ end
31
+
32
+ def is_percent?
33
+ sql_column.cast_type.scale == 4
34
+ end
35
+
36
+ def is_currency?
37
+ sql_column.cast_type.scale == 2 || sql_column.cast_type.scale == 5
38
+ end
39
+
40
+ # Returns the column's expansions
41
+ #
42
+ # == Example
43
+ # array of strings (default expansion column is `type`)::
44
+ # `update_column :override, expand_on: ["Approved", "Submitted"]`
45
+ # array of hashes::
46
+ # `update_column :amount, expand_on: [{value: "Single", column_name: "product_option"}, {value: "Target", column_name: "product_option"}]`
47
+ # proc::
48
+ # `update_column :amount, expand_on: proc { ProductType.all.map(&:name) }
49
+ #
50
+ # @return [Array] Array of expansion hashes
51
+ def expand_on
52
+ return @expand_on if @expand_on_memoized
53
+ @expand_on_memoized = true
54
+ @expand_on = @expand_on.call if @expand_on.is_a?(Proc)
55
+ @expand_on = Array(@expand_on).map do |expansion|
56
+ expansion.is_a?(Hash) ? expansion : { value: expansion, column_name: "type" }
57
+ end
58
+ end
59
+
60
+ def description
61
+ @description ||= ActiveRecord::Base.connection.execute(<<-SQL).values[0][0]
62
+ SELECT d.description
63
+ FROM pg_attribute a
64
+ LEFT JOIN pg_description d
65
+ ON a.attrelid = d.objoid AND a.attnum = d.objsubid
66
+ WHERE a.attrelid = '#{table_name}'::regclass
67
+ AND a.attnum > 0
68
+ AND NOT a.attisdropped
69
+ AND a.attname = '#{name}';
70
+ SQL
71
+ end
72
+
73
+ def partial_type_id
74
+ if @partial_type_id.is_a?(Proc)
75
+ @partial_type_id.call
76
+ else
77
+ @partial_type_id
78
+ end
79
+ end
80
+
81
+ def type
82
+ @type ||= ::Flattener::Column::Types.sql_type_to_type(sql_type)
83
+ end
84
+
85
+ def self.build_sql_column(name, sql_type)
86
+ cast_type =
87
+ ActiveRecord::Base.connection.send(:lookup_cast_type, sql_type) ||
88
+ ActiveRecord::Type.lookup(sql_type.to_s.sub(/\(.+\)\z/, "").to_sym)
89
+
90
+ OpenStruct.new(
91
+ name: name.to_s,
92
+ sql_type: sql_type,
93
+ cast_type: cast_type
94
+ )
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,49 @@
1
+ # typed: false
2
+ # frozen_string_literal: true
3
+
4
+ require "delegate"
5
+
6
+ # Turns a Flattener class into a pivoted Flattener class.
7
+ # It is recommended that you use this with duplicates of the
8
+ # original flattener class.
9
+ #
10
+ #=== Examples
11
+ # flattener_klass = Flattener.for(Phone).dup
12
+ # flattener_klass.prepend(Flattener::Pivot)
13
+ module Flattener
14
+ module Pivot
15
+ def self.prepended(klass)
16
+ klass.singleton_class.prepend(ClassMethods)
17
+
18
+ class << klass
19
+ attr_accessor :is_lookup
20
+ end
21
+ end
22
+
23
+ module SingletonClassMethods
24
+ delegate :associations, to: :class
25
+
26
+ delegate :columns, to: :class
27
+ end
28
+
29
+ module ClassMethods
30
+ def new
31
+ super.tap do |instance|
32
+ instance.singleton_class.prepend(SingletonClassMethods)
33
+ end
34
+ end
35
+
36
+ def associations
37
+ pivot_associations
38
+ end
39
+
40
+ def columns
41
+ pivot_columns
42
+ end
43
+
44
+ def is_lookup?
45
+ !!is_lookup
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,33 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ module Flattener
5
+ # When included this module removes the dependencies of Flattener::Base on the
6
+ # table underlying the model class by essentially wiping the slate and starting
7
+ # from an empty set of columns and associations. Think of it as the equivalent
8
+ # of reset.css for flatteners. This is useful for cases where data warehouse
9
+ # table does not correspond very directly to a transactional table or model.
10
+ #
11
+ # Include this module into a flattener class:
12
+ #
13
+ # class MyFlattener < Flattener::Base
14
+ # include Flattener::Reset
15
+ # # Yay, columns and associations are empty and ready to be configured!
16
+ # # ...
17
+ # end
18
+ module Reset
19
+ def self.included(klass)
20
+ klass.singleton_class.prepend(ClassInstanceMethods)
21
+ end
22
+
23
+ module ClassInstanceMethods
24
+ def model_associations
25
+ @model_associations ||= []
26
+ end
27
+
28
+ def model_columns
29
+ @model_columns ||= []
30
+ end
31
+ end
32
+ end
33
+ end