rails_base 0.51.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 (194) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +32 -0
  4. data/Rakefile +32 -0
  5. data/app/assets/config/rails_base/manifest.js +3 -0
  6. data/app/assets/images/rails_base/favicon.ico +0 -0
  7. data/app/assets/javascripts/rails_base/admin.js +2 -0
  8. data/app/assets/javascripts/rails_base/application.js +22 -0
  9. data/app/assets/javascripts/rails_base/cable.js +13 -0
  10. data/app/assets/javascripts/rails_base/mfa_auth.coffee +3 -0
  11. data/app/assets/javascripts/rails_base/secondary_authentication.coffee +3 -0
  12. data/app/assets/javascripts/rails_base/sessions.js +152 -0
  13. data/app/assets/javascripts/rails_base/user_settings.coffee +3 -0
  14. data/app/assets/stylesheets/rails_base/admin.css +4 -0
  15. data/app/assets/stylesheets/rails_base/application.scss +15 -0
  16. data/app/assets/stylesheets/rails_base/mfa_auth.scss +3 -0
  17. data/app/assets/stylesheets/rails_base/scaffolds.scss +84 -0
  18. data/app/assets/stylesheets/rails_base/secondary_authentication.scss +3 -0
  19. data/app/assets/stylesheets/rails_base/user_settings.scss +3 -0
  20. data/app/controllers/rails_base/admin_controller.rb +315 -0
  21. data/app/controllers/rails_base/application_controller.rb +153 -0
  22. data/app/controllers/rails_base/errors_controller.rb +29 -0
  23. data/app/controllers/rails_base/mfa_auth_controller.rb +50 -0
  24. data/app/controllers/rails_base/secondary_authentication_controller.rb +224 -0
  25. data/app/controllers/rails_base/switch_user_controller.rb +29 -0
  26. data/app/controllers/rails_base/user_settings_controller.rb +81 -0
  27. data/app/controllers/rails_base/users/passwords_controller.rb +19 -0
  28. data/app/controllers/rails_base/users/registrations_controller.rb +80 -0
  29. data/app/controllers/rails_base/users/sessions_controller.rb +108 -0
  30. data/app/helpers/rails_base/admin_helper.rb +107 -0
  31. data/app/helpers/rails_base/appearance_helper.rb +58 -0
  32. data/app/helpers/rails_base/application_helper.rb +26 -0
  33. data/app/helpers/rails_base/capture_reference_helper.rb +57 -0
  34. data/app/helpers/rails_base/mfa_auth_helper.rb +2 -0
  35. data/app/helpers/rails_base/secondary_authentication_helper.rb +2 -0
  36. data/app/helpers/rails_base/user_field_validators.rb +108 -0
  37. data/app/helpers/rails_base/user_settings_helper.rb +22 -0
  38. data/app/jobs/rails_base/application_job.rb +10 -0
  39. data/app/jobs/twilio_job.rb +9 -0
  40. data/app/mailers/rails_base/application_mailer.rb +9 -0
  41. data/app/mailers/rails_base/email_verification_mailer.rb +22 -0
  42. data/app/mailers/rails_base/event_mailer.rb +16 -0
  43. data/app/models/admin_action.rb +119 -0
  44. data/app/models/rails_base/application_record.rb +22 -0
  45. data/app/models/rails_base/user_constants.rb +28 -0
  46. data/app/models/secret.rb +37 -0
  47. data/app/models/short_lived_data.rb +132 -0
  48. data/app/models/user.rb +143 -0
  49. data/app/services/rails_base/admin_risky_mfa_send.rb +80 -0
  50. data/app/services/rails_base/admin_update_attribute.rb +100 -0
  51. data/app/services/rails_base/authentication/authenticate_user.rb +28 -0
  52. data/app/services/rails_base/authentication/constants.rb +60 -0
  53. data/app/services/rails_base/authentication/decision_twofa_type.rb +76 -0
  54. data/app/services/rails_base/authentication/destroy_user.rb +45 -0
  55. data/app/services/rails_base/authentication/mfa_set_encrypt_token.rb +32 -0
  56. data/app/services/rails_base/authentication/mfa_validator.rb +88 -0
  57. data/app/services/rails_base/authentication/modify_password.rb +67 -0
  58. data/app/services/rails_base/authentication/send_forgot_password.rb +26 -0
  59. data/app/services/rails_base/authentication/send_login_mfa_to_user.rb +77 -0
  60. data/app/services/rails_base/authentication/send_verification_email.rb +103 -0
  61. data/app/services/rails_base/authentication/session_token_verifier.rb +31 -0
  62. data/app/services/rails_base/authentication/single_sign_on_create.rb +44 -0
  63. data/app/services/rails_base/authentication/single_sign_on_send.rb +101 -0
  64. data/app/services/rails_base/authentication/single_sign_on_verify.rb +42 -0
  65. data/app/services/rails_base/authentication/sso_verify_email.rb +43 -0
  66. data/app/services/rails_base/authentication/update_phone_send_verification.rb +46 -0
  67. data/app/services/rails_base/authentication/verify_forgot_password.rb +46 -0
  68. data/app/services/rails_base/email_change.rb +20 -0
  69. data/app/services/rails_base/encryption.rb +87 -0
  70. data/app/services/rails_base/name_change.rb +71 -0
  71. data/app/services/rails_base/service_base.rb +65 -0
  72. data/app/services/rails_base/service_logging.rb +23 -0
  73. data/app/views/layouts/rails_base/application.html.erb +185 -0
  74. data/app/views/layouts/rails_base/mailer.html.erb +13 -0
  75. data/app/views/layouts/rails_base/mailer.text.erb +1 -0
  76. data/app/views/new.html.erb +4 -0
  77. data/app/views/rails_base/admin/history.html.erb +26 -0
  78. data/app/views/rails_base/admin/index.html.erb +149 -0
  79. data/app/views/rails_base/admin/show_config.html.erb +18 -0
  80. data/app/views/rails_base/devise/confirmations/new.html.erb +16 -0
  81. data/app/views/rails_base/devise/mailer/confirmation_instructions.html.erb +5 -0
  82. data/app/views/rails_base/devise/mailer/email_changed.html.erb +7 -0
  83. data/app/views/rails_base/devise/mailer/password_change.html.erb +3 -0
  84. data/app/views/rails_base/devise/mailer/reset_password_instructions.html.erb +8 -0
  85. data/app/views/rails_base/devise/mailer/unlock_instructions.html.erb +7 -0
  86. data/app/views/rails_base/devise/passwords/edit.html.erb +25 -0
  87. data/app/views/rails_base/devise/passwords/new.html.erb +27 -0
  88. data/app/views/rails_base/devise/registrations/edit.html.erb +43 -0
  89. data/app/views/rails_base/devise/registrations/new.html.erb +123 -0
  90. data/app/views/rails_base/devise/sessions/new.html.erb +4 -0
  91. data/app/views/rails_base/devise/shared/_error_messages.html.erb +15 -0
  92. data/app/views/rails_base/devise/shared/_links.html.erb +25 -0
  93. data/app/views/rails_base/devise/unlocks/new.html.erb +16 -0
  94. data/app/views/rails_base/email_verification_mailer/email_verification.html.erb +25 -0
  95. data/app/views/rails_base/email_verification_mailer/event.html.erb +20 -0
  96. data/app/views/rails_base/email_verification_mailer/forgot_password.html.erb +22 -0
  97. data/app/views/rails_base/errors/internal_error.html.erb +1 -0
  98. data/app/views/rails_base/errors/not_found.html.erb +1 -0
  99. data/app/views/rails_base/errors/unacceptable.html.erb +1 -0
  100. data/app/views/rails_base/event_mailer/event.html.erb +10 -0
  101. data/app/views/rails_base/mfa_auth/mfa_code.html.erb +10 -0
  102. data/app/views/rails_base/secondary_authentication/after_email_login_session_new.html.erb +3 -0
  103. data/app/views/rails_base/secondary_authentication/forgot_password.html.erb +9 -0
  104. data/app/views/rails_base/secondary_authentication/remove_me.html.erb +1 -0
  105. data/app/views/rails_base/secondary_authentication/static.html.erb +5 -0
  106. data/app/views/rails_base/shared/_admin_actions_modal.html.erb +65 -0
  107. data/app/views/rails_base/shared/_admin_config_class.html.erb +52 -0
  108. data/app/views/rails_base/shared/_admin_history.html.erb +86 -0
  109. data/app/views/rails_base/shared/_admin_modify_email.html.erb +78 -0
  110. data/app/views/rails_base/shared/_admin_modify_name.html.erb +107 -0
  111. data/app/views/rails_base/shared/_admin_modify_phone.html.erb +87 -0
  112. data/app/views/rails_base/shared/_admin_modify_text.html.erb +35 -0
  113. data/app/views/rails_base/shared/_admin_risky_change.html.erb +57 -0
  114. data/app/views/rails_base/shared/_admin_risky_mfa.html.erb +74 -0
  115. data/app/views/rails_base/shared/_admin_selector_dropdown.html.erb +70 -0
  116. data/app/views/rails_base/shared/_admin_toggle_button.html.erb +72 -0
  117. data/app/views/rails_base/shared/_admin_warning_alert.html.erb +7 -0
  118. data/app/views/rails_base/shared/_appearance_mode_selector.html.erb +183 -0
  119. data/app/views/rails_base/shared/_custom_form_validation_javascript.html.erb +129 -0
  120. data/app/views/rails_base/shared/_enable_mfa_auth_modal.html.erb +105 -0
  121. data/app/views/rails_base/shared/_error_pages.html.erb +123 -0
  122. data/app/views/rails_base/shared/_logged_in_header.html.erb +123 -0
  123. data/app/views/rails_base/shared/_logged_out_header.html.erb +14 -0
  124. data/app/views/rails_base/shared/_mfa_input_layout.html.erb +5 -0
  125. data/app/views/rails_base/shared/_mfa_input_layout_default.html.erb +97 -0
  126. data/app/views/rails_base/shared/_mfa_input_layout_fallback.html.erb +55 -0
  127. data/app/views/rails_base/shared/_modify_mfa_auth_modal.html.erb +20 -0
  128. data/app/views/rails_base/shared/_password_confirm_javascript.html.erb +71 -0
  129. data/app/views/rails_base/shared/_reset_password_form.html.erb +111 -0
  130. data/app/views/rails_base/shared/_session_create_form.html.erb +32 -0
  131. data/app/views/rails_base/shared/_session_timeout_modal.html.erb +76 -0
  132. data/app/views/rails_base/switch_user/_widget.html.erb +5 -0
  133. data/app/views/rails_base/user_settings/_confirm_destroy_user.html.erb +42 -0
  134. data/app/views/rails_base/user_settings/_destroy_user.html.erb +106 -0
  135. data/app/views/rails_base/user_settings/_modify_name.html.erb +71 -0
  136. data/app/views/rails_base/user_settings/_modify_password.html.erb +101 -0
  137. data/app/views/rails_base/user_settings/_modify_password_update_password.html.erb +2 -0
  138. data/app/views/rails_base/user_settings/index.html.erb +54 -0
  139. data/config/initializers/01_rails_config.rb +19 -0
  140. data/config/initializers/admin_action_helper.rb +88 -0
  141. data/config/initializers/browser.rb +4 -0
  142. data/config/initializers/default_logged_in_headers.rb +23 -0
  143. data/config/initializers/devise.rb +314 -0
  144. data/config/initializers/encryption.rb +2 -0
  145. data/config/initializers/switch_user.rb +58 -0
  146. data/config/initializers/switch_user_helper.rb +29 -0
  147. data/config/locales/devise.en.yml +65 -0
  148. data/config/locales/en.yml +58 -0
  149. data/config/routes.rb +114 -0
  150. data/db/migrate/20210212175453_devise_create_rails_base_users.rb +56 -0
  151. data/db/migrate/20210212190537_create_rails_base_short_lived_data.rb +19 -0
  152. data/db/migrate/20210212192645_create_rails_base_secrets.rb +11 -0
  153. data/db/migrate/20210406015744_create_rails_base_admin_actions.rb +17 -0
  154. data/db/seeds.rb +23 -0
  155. data/lib/link_decision_helper.rb +71 -0
  156. data/lib/rails_base.rb +50 -0
  157. data/lib/rails_base/admin/action_cache.rb +99 -0
  158. data/lib/rails_base/admin/action_helper.rb +134 -0
  159. data/lib/rails_base/admin/default_index_tile.rb +176 -0
  160. data/lib/rails_base/admin/index_tile.rb +186 -0
  161. data/lib/rails_base/config.rb +52 -0
  162. data/lib/rails_base/configuration/active_job.rb +38 -0
  163. data/lib/rails_base/configuration/admin.rb +231 -0
  164. data/lib/rails_base/configuration/app.rb +52 -0
  165. data/lib/rails_base/configuration/appearance.rb +131 -0
  166. data/lib/rails_base/configuration/authentication.rb +37 -0
  167. data/lib/rails_base/configuration/base.rb +209 -0
  168. data/lib/rails_base/configuration/display/background_color.rb +25 -0
  169. data/lib/rails_base/configuration/display/btn_danger.rb +25 -0
  170. data/lib/rails_base/configuration/display/btn_dark.rb +25 -0
  171. data/lib/rails_base/configuration/display/btn_info.rb +25 -0
  172. data/lib/rails_base/configuration/display/btn_light.rb +25 -0
  173. data/lib/rails_base/configuration/display/btn_primary.rb +25 -0
  174. data/lib/rails_base/configuration/display/btn_secondary.rb +25 -0
  175. data/lib/rails_base/configuration/display/btn_success.rb +25 -0
  176. data/lib/rails_base/configuration/display/btn_warning.rb +25 -0
  177. data/lib/rails_base/configuration/display/footer.rb +54 -0
  178. data/lib/rails_base/configuration/display/navbar.rb +25 -0
  179. data/lib/rails_base/configuration/display/table_body.rb +25 -0
  180. data/lib/rails_base/configuration/display/table_header.rb +25 -0
  181. data/lib/rails_base/configuration/display/text.rb +26 -0
  182. data/lib/rails_base/configuration/exceptions_app.rb +25 -0
  183. data/lib/rails_base/configuration/login_behavior.rb +17 -0
  184. data/lib/rails_base/configuration/mailer.rb +116 -0
  185. data/lib/rails_base/configuration/mfa.rb +84 -0
  186. data/lib/rails_base/configuration/owner.rb +17 -0
  187. data/lib/rails_base/configuration/redis.rb +29 -0
  188. data/lib/rails_base/configuration/user.rb +43 -0
  189. data/lib/rails_base/engine.rb +51 -0
  190. data/lib/rails_base/version.rb +10 -0
  191. data/lib/tasks/rails_base_tasks.rake +4 -0
  192. data/lib/twilio_helper.rb +26 -0
  193. data/lib/velocity_limiter.rb +91 -0
  194. metadata +619 -0
@@ -0,0 +1,52 @@
1
+ require 'rails_base/configuration/base'
2
+
3
+ module RailsBase
4
+ module Configuration
5
+ class App < Base
6
+ DEFAULT_VALUES = {
7
+ base_url: {
8
+ type: :string,
9
+ default: ENV.fetch('BASE_URL', 'http://localhost'),
10
+ description: 'Base url. Used for things like SSO.'
11
+ },
12
+ base_port: {
13
+ type: :string_nil,
14
+ default: ENV.fetch('BASE_URL_PORT', nil),
15
+ description: 'Base port. Used for things like SSO.'
16
+ },
17
+ web_name_logged_in: {
18
+ type: :string_proc,
19
+ default: ->(user) { Rails.application.class.parent_name },
20
+ description: 'Name of the application when authenticated user is present. Name in the tab of the browser. Allows for dynamic tab names'
21
+ },
22
+ web_name_logged_out: {
23
+ type: :string_proc,
24
+ default: ->(*) { Rails.application.class.parent_name },
25
+ description: 'Name of the application when no authenticated user. Name in the tab of the browser. Allows for dynamic tab names'
26
+ },
27
+ web_title_logged_in: {
28
+ type: :string_proc,
29
+ default: ->(user) { Rails.application.class.parent_name },
30
+ description: 'Title in nav for the web when logged in. String or proc accepted. When proc, current user will be passed in.'
31
+ },
32
+ web_title_logged_out: {
33
+ type: :string_proc,
34
+ default: ->(*) { Rails.application.class.parent_name },
35
+ description: 'Title in nav for the web when logged in. String or proc accepted. When proc, current user will be passed in.'
36
+ },
37
+ communication_name: {
38
+ type: :string_proc,
39
+ default: ->(*) { Rails.application.class.parent_name },
40
+ description: 'Name used when communicating with users.'
41
+ },
42
+ favicon_path: {
43
+ type: :string_nil,
44
+ default: 'rails_base/favicon.ico',
45
+ description: 'Favicon path'
46
+ },
47
+ }
48
+
49
+ attr_accessor *DEFAULT_VALUES.keys
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,131 @@
1
+ require 'rails_base/configuration/base'
2
+ require 'rails_base/configuration/display/table_header'
3
+ require 'rails_base/configuration/display/table_body'
4
+ require 'rails_base/configuration/display/background_color'
5
+ require 'rails_base/configuration/display/navbar'
6
+ require 'rails_base/configuration/display/text'
7
+ require 'rails_base/configuration/display/footer'
8
+ require 'rails_base/configuration/display/btn_primary'
9
+ require 'rails_base/configuration/display/btn_secondary'
10
+ require 'rails_base/configuration/display/btn_success'
11
+ require 'rails_base/configuration/display/btn_danger'
12
+ require 'rails_base/configuration/display/btn_warning'
13
+ require 'rails_base/configuration/display/btn_info'
14
+ require 'rails_base/configuration/display/btn_light'
15
+ require 'rails_base/configuration/display/btn_dark'
16
+
17
+ module RailsBase
18
+ module Configuration
19
+ class Appearance < Base
20
+ BUTTONS = [
21
+ :btn_primary,
22
+ :btn_secondary,
23
+ :btn_success,
24
+ :btn_danger,
25
+ :btn_warning,
26
+ :btn_info,
27
+ :btn_light,
28
+ :btn_dark,
29
+ ]
30
+
31
+ DOWNSTREAM_CLASSES = [
32
+ :t_header,
33
+ :t_body,
34
+ :bg_color,
35
+ :navbar,
36
+ :text,
37
+ :footer,
38
+
39
+ ] + BUTTONS
40
+
41
+ SKIP_DOWNSTREAM_CLASSES = [:footer]
42
+ DARK_MODE = :dark
43
+ LIGHT_MODE = :light
44
+ MATCH_OS = :match_os
45
+
46
+ ALLOWABLE_TYPES = {
47
+ DARK_MODE => 'Dark Mode',
48
+ LIGHT_MODE => 'Light Mode',
49
+ }
50
+
51
+ APPEARANCE_TYPES = ALLOWABLE_TYPES.merge(MATCH_OS => 'Match System')
52
+
53
+ DEFAULT_VALUES = {
54
+ enabled: {
55
+ type: :boolean,
56
+ default: true,
57
+ description: 'When disabled, the user will be forced into default_mode when appropriate'
58
+ },
59
+ default_mode: {
60
+ type: :values,
61
+ expect_values: APPEARANCE_TYPES.keys,
62
+ default: LIGHT_MODE,
63
+ description: 'Default mode to set when mode not found in cookies/session',
64
+ },
65
+
66
+ math_os_dark: {
67
+ type: :values,
68
+ expect_values: ALLOWABLE_TYPES.keys,
69
+ default: DARK_MODE,
70
+ description: 'Mode to set when OS returns dark mode (useful when more than light/dark mode',
71
+ },
72
+ }
73
+
74
+ attr_accessor *DEFAULT_VALUES.keys
75
+ attr_reader *DOWNSTREAM_CLASSES
76
+
77
+ def initialize
78
+ #####
79
+ # all display classes are required to have ALLOWABLE_TYPES as default values
80
+ #####
81
+ @t_header = Configuration::Display::TableHeader.new
82
+ @t_body = Configuration::Display::TableBody.new
83
+ @bg_color = Configuration::Display::BackgroundColor.new
84
+ @navbar = Configuration::Display::Navbar.new
85
+ @text = Configuration::Display::Text.new
86
+ @footer = Configuration::Display::Footer.new
87
+
88
+ @btn_primary = Configuration::Display::BtnPrimary.new
89
+ @btn_secondary = Configuration::Display::BtnSecondary.new
90
+ @btn_success = Configuration::Display::BtnSuccess.new
91
+ @btn_danger = Configuration::Display::BtnDanger.new
92
+ @btn_warning = Configuration::Display::BtnWarning.new
93
+ @btn_info = Configuration::Display::BtnInfo.new
94
+ @btn_light = Configuration::Display::BtnLight.new
95
+ @btn_dark = Configuration::Display::BtnDark.new
96
+
97
+ _validate_values
98
+ super()
99
+ end
100
+
101
+ def validate!
102
+ super()
103
+ DOWNSTREAM_CLASSES.each do |variable|
104
+ instance_variable_get("@#{variable}").validate!
105
+ end
106
+ end
107
+
108
+ def assign_default_values!
109
+ super()
110
+ DOWNSTREAM_CLASSES.each do |variable|
111
+ instance_variable_get("@#{variable}").assign_default_values!
112
+ end
113
+ end
114
+
115
+ private
116
+ def _validate_values
117
+ DOWNSTREAM_CLASSES.each do |var|
118
+ next if SKIP_DOWNSTREAM_CLASSES.include?(var)
119
+
120
+ ALLOWABLE_TYPES.each do |k, v|
121
+ next if public_send(var).respond_to?("#{k}_mode")
122
+
123
+ raise ArgumentError, "#{var} does not respond to #{k}_mode"
124
+ end
125
+ end
126
+ end
127
+ end
128
+ end
129
+ end
130
+
131
+
@@ -0,0 +1,37 @@
1
+ require 'rails_base/configuration/base'
2
+
3
+ module RailsBase
4
+ module Configuration
5
+ class Authentication < Base
6
+ DEFAULT_SESSION = 60.days
7
+ MIN_SESSION = 5.minutes
8
+
9
+ DEFAULT_MFA_TIME = 7.day
10
+ MIN_MFA_TIME = 1.day
11
+
12
+ DEFAULT_VALUES = {
13
+ session_timeout: {
14
+ type: :duration,
15
+ default: ENV.fetch('SESSION_TIMEOUT_IN_SECONDS', DEFAULT_SESSION).to_i.seconds,
16
+ custom: ->(val) { val.to_i >= MIN_SESSION },
17
+ msg: "session_timeout must be a duration. Greater than #{MIN_SESSION}",
18
+ on_assignment: ->(val, _instance) { Devise.timeout_in = val },
19
+ description: 'Debug purposes. How long to keep admin_velocity_max attempts',
20
+ },
21
+ session_timeout_warning: {
22
+ type: :boolean,
23
+ default: true,
24
+ description: 'Display a timeout warning. When disabled, user will be logged out without warning',
25
+ },
26
+ mfa_time_duration: {
27
+ type: :duration,
28
+ default: DEFAULT_MFA_TIME,
29
+ custom: ->(val) { val.to_i > MIN_MFA_TIME },
30
+ msg: "mfa_time_duration must be a duration. Greater than #{MIN_MFA_TIME}",
31
+ description: 'Max time between when MFA will be required',
32
+ }
33
+ }
34
+ attr_accessor *DEFAULT_VALUES.keys
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,209 @@
1
+ module RailsBase
2
+ module Configuration
3
+ class Base
4
+ class InvalidConfiguration < StandardError; end;
5
+ class InvalidCustomConfiguration < StandardError; end;
6
+ class ConfigurationAlreadyEstablished < StandardError; end;
7
+
8
+ def self._allow_write_block?
9
+ true
10
+ end
11
+
12
+ def self._unset_allow_write!
13
+ define_singleton_method('_allow_write_block?') do
14
+ false
15
+ end
16
+ end
17
+
18
+ ALLOWED_TYPES = {
19
+ boolean: -> (val) { [TrueClass, FalseClass].include?(val.class) },
20
+ proc: -> (val) { [Proc].include?(val.class) },
21
+ integer: -> (val) { [Integer].include?(val.class) },
22
+ string: -> (val) { [String].include?(val.class) },
23
+ symbol: -> (val) { [Symbol].include?(val.class) },
24
+ symbol_class: -> (val) { [Symbol].include?(val.class) || val.superclass === ActiveJob::QueueAdapters },
25
+ duration: -> (val) { [ActiveSupport::Duration].include?(val.class) },
26
+ string_nil: -> (val) { [String, NilClass].include?(val.class) },
27
+ string_proc: -> (val) { [String, Proc].include?(val.class) },
28
+ array: -> (val) { [Array].include?(val.class) },
29
+ hash: -> (val) { [Hash].include?(val.class) },
30
+ path: -> (val) { [Pathname].include?(val.class) },
31
+ klass: -> (_val) { true },
32
+ values: -> (_val) { true },
33
+ }
34
+
35
+ def initialize
36
+ override_methods!
37
+ assign_default_values!
38
+ def_convenience_methods
39
+ end
40
+
41
+ # on any object you Base inherited object yoyu can call
42
+ # `.dig(:chain1, :chain2, :chain3)
43
+ def dig(*args, default: nil, &block)
44
+ current = self.public_send(args[0])
45
+
46
+ args[1..-1].each do |key|
47
+ return current || default if current.nil?
48
+
49
+ current = current.public_send(key)
50
+ end
51
+ current
52
+ rescue StandardError
53
+ if block_given?
54
+ yield block
55
+ else
56
+ default
57
+ end
58
+ end
59
+
60
+ def assign_default_values!
61
+ self.class::DEFAULT_VALUES.each do |key, object|
62
+ val = object[:default_assign_on_boot] ? object[:default_assign_on_boot].call : object[:default]
63
+ public_send(:"#{key}=", val)
64
+ end
65
+ true
66
+ end
67
+
68
+ def override_methods!
69
+ self.class::DEFAULT_VALUES.each do |key, object|
70
+ self.class.define_method(:"#{key}=") do |value|
71
+ raise ConfigurationAlreadyEstablished, "Unable to assign [#{_name}.#{key}]. Assignment must happen on boot" unless self.class._allow_write_block?
72
+
73
+ instance_variable_set(:"@#{key}", value)
74
+ end
75
+ if object[:type] == :array
76
+ self.class.define_method(:"#{key}<<") do |value|
77
+ raise ConfigurationAlreadyEstablished, "Unable to assign [#{_name}.#{key}]. Assignment must happen on boot" unless self.class._allow_write_block?
78
+
79
+ curr = instance_variable_get(:"@#{key}")
80
+ curr << value
81
+ instance_variable_set(:"@#{key}", curr)
82
+ end
83
+ end
84
+ if object[:type] == :hash
85
+ self.class.define_method(:"#{key}.merge") do |value|
86
+ raise ConfigurationAlreadyEstablished, "Unable to assign [#{_name}.#{key}]. Assignment must happen on boot" unless self.class._allow_write_block?
87
+
88
+ curr = instance_variable_get(:"@#{key}")
89
+ curr.merge(value)
90
+ instance_variable_set(:"@#{key}", curr)
91
+ end
92
+ end
93
+ end
94
+ end
95
+
96
+ def validate!
97
+ custom_validations
98
+ self.class::DEFAULT_VALUES.each do |key, object|
99
+ value = instance_variable_get("@#{key}".to_sym)
100
+ validate_var!(key: key, var: value, type: object[:type])
101
+ validate_custom_rule!(var: value, custom: object[:custom], key: key, msg: object[:msg])
102
+ validate_klass_type!(key: key, var: value, type: object[:type], klass_type: object[:klass_type])
103
+ validate_values_included!(key: key, var: value, type: object[:type], expect_values: object[:expect_values])
104
+ if object[:on_assignment]
105
+ if object[:on_assignment].is_a? Array
106
+ object[:on_assignment].each do |elem|
107
+ elem.call(value, self)
108
+ end
109
+ else
110
+ object[:on_assignment].call(value, self)
111
+ end
112
+ end
113
+ end
114
+ true
115
+ end
116
+
117
+ private
118
+
119
+ def custom_validations
120
+ end
121
+
122
+ def def_convenience_methods
123
+ self.class::DEFAULT_VALUES.each do |key, object|
124
+ if object[:type] == :boolean
125
+ self.class.define_method("#{key}?") do
126
+ return false unless dependents_true?(key)
127
+
128
+ public_send(key)
129
+ end
130
+ elsif object[:type] == :proc
131
+ self.class.define_method("#{key}?") do |current_user|
132
+ return false unless dependents_true?(key)
133
+
134
+ public_send(key).call(current_user)
135
+ end
136
+ elsif object[:type] == :string_proc
137
+ self.class.define_method("#{key}") do |*args|
138
+ return false unless dependents_true?(key)
139
+
140
+ if instance_variable_get("@#{key}".to_sym).is_a? Proc
141
+ instance_variable_get("@#{key}".to_sym).call(args[0])
142
+ else
143
+ instance_variable_get("@#{key}".to_sym)
144
+ end
145
+ end
146
+ else
147
+ self.class.define_method("#{key}") do
148
+ return false unless dependents_true?(key)
149
+
150
+ instance_variable_get("@#{key}".to_sym)
151
+ end
152
+ end
153
+ end
154
+ end
155
+
156
+ def dependents_true?(key)
157
+ dependents = self.class::DEFAULT_VALUES[key][:dependents] || []
158
+
159
+ dependents.all? { |s| s.call(self) }
160
+ end
161
+
162
+ def validate_var!(var:, type:, key:)
163
+ proc = ALLOWED_TYPES.fetch(type)
164
+ return if proc.call(var)
165
+
166
+ raise InvalidConfiguration, "#{_name}.#{key} expects a #{type}."
167
+ end
168
+
169
+ def validate_klass_type!(var:, type:, key:, klass_type:)
170
+ return if klass_type.nil?
171
+
172
+ boolean =
173
+ if var.is_a? Array
174
+ var.all? { |s| klass_type.include?(s.class) }
175
+ else
176
+ klass_type.include?(var.class)
177
+ end
178
+ return if boolean
179
+
180
+ raise InvalidConfiguration, "#{_name}.#{key} expects all members to be a #{klass_type}. Received: [#{var.class}]"
181
+ end
182
+
183
+ def validate_values_included!(key:, var:, type:, expect_values:)
184
+ return if expect_values.nil?
185
+ values =
186
+ if expect_values.is_a? Proc
187
+ expect_values.call(self)
188
+ else
189
+ expect_values
190
+ end
191
+
192
+ return if values.include?(var)
193
+
194
+ raise InvalidConfiguration, "#{_name}.#{key} expects value to be included in [#{values}]. Received: [#{var}]"
195
+ end
196
+
197
+ def _name
198
+ self.class.name.demodulize
199
+ end
200
+
201
+ def validate_custom_rule!(var:, custom:, key:, msg:)
202
+ return if custom.nil?
203
+ return if custom.call(var)
204
+
205
+ raise InvalidCustomConfiguration, msg
206
+ end
207
+ end
208
+ end
209
+ end
@@ -0,0 +1,25 @@
1
+ require 'rails_base/configuration/base'
2
+
3
+ module RailsBase
4
+ module Configuration
5
+ module Display
6
+ class BackgroundColor < Base
7
+
8
+ DEFAULT_VALUES = {
9
+ dark_mode: {
10
+ type: :string_nil,
11
+ default: 'bg-secondary',
12
+ description: 'Background to use in Dark mode'
13
+ },
14
+ light_mode: {
15
+ type: :string_nil,
16
+ default: '',
17
+ description: 'Background to use in light mode'
18
+ },
19
+ }
20
+
21
+ attr_accessor *DEFAULT_VALUES.keys
22
+ end
23
+ end
24
+ end
25
+ end