rhino_project_core 0.20.0.beta.18

Sign up to get free protection for your applications and to get access to all the features.
Files changed (117) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +28 -0
  3. data/Rakefile +34 -0
  4. data/app/assets/stripe_flow.png +0 -0
  5. data/app/controllers/concerns/rhino/authenticated.rb +18 -0
  6. data/app/controllers/concerns/rhino/error_handling.rb +58 -0
  7. data/app/controllers/concerns/rhino/paper_trail_whodunnit.rb +11 -0
  8. data/app/controllers/concerns/rhino/permit.rb +38 -0
  9. data/app/controllers/concerns/rhino/set_current_user.rb +13 -0
  10. data/app/controllers/rhino/account_controller.rb +34 -0
  11. data/app/controllers/rhino/active_model_extension_controller.rb +52 -0
  12. data/app/controllers/rhino/base_controller.rb +23 -0
  13. data/app/controllers/rhino/crud_controller.rb +57 -0
  14. data/app/controllers/rhino/simple_controller.rb +11 -0
  15. data/app/controllers/rhino/simple_stream_controller.rb +12 -0
  16. data/app/helpers/rhino/omniauth_helper.rb +67 -0
  17. data/app/helpers/rhino/policy_helper.rb +42 -0
  18. data/app/helpers/rhino/segment_helper.rb +62 -0
  19. data/app/models/rhino/account.rb +13 -0
  20. data/app/models/rhino/current.rb +7 -0
  21. data/app/models/rhino/user.rb +44 -0
  22. data/app/overrides/active_record/autosave_association_override.rb +18 -0
  23. data/app/overrides/active_record/delegated_type_override.rb +14 -0
  24. data/app/overrides/activestorage/direct_uploads_controller_override.rb +23 -0
  25. data/app/overrides/activestorage/redirect_controller_override.rb +21 -0
  26. data/app/overrides/activestorage/redirect_representation_controller_override.rb +21 -0
  27. data/app/overrides/devise_token_auth/confirmations_controller_override.rb +14 -0
  28. data/app/overrides/devise_token_auth/omniauth_callbacks_controller_override.rb +45 -0
  29. data/app/overrides/devise_token_auth/passwords_controller_override.rb +9 -0
  30. data/app/overrides/devise_token_auth/registrations_controller_override.rb +20 -0
  31. data/app/overrides/devise_token_auth/sessions_controller_override.rb +26 -0
  32. data/app/overrides/devise_token_auth/token_validations_controller_override.rb +18 -0
  33. data/app/policies/rhino/account_policy.rb +27 -0
  34. data/app/policies/rhino/active_storage_attachment_policy.rb +16 -0
  35. data/app/policies/rhino/admin_policy.rb +20 -0
  36. data/app/policies/rhino/base_policy.rb +72 -0
  37. data/app/policies/rhino/crud_policy.rb +109 -0
  38. data/app/policies/rhino/editor_policy.rb +12 -0
  39. data/app/policies/rhino/global_policy.rb +8 -0
  40. data/app/policies/rhino/resource_info_policy.rb +9 -0
  41. data/app/policies/rhino/user_policy.rb +20 -0
  42. data/app/policies/rhino/viewer_policy.rb +19 -0
  43. data/app/resources/rhino/info_graph.rb +41 -0
  44. data/app/resources/rhino/open_api_info.rb +108 -0
  45. data/config/routes.rb +19 -0
  46. data/db/migrate/20180101000000_devise_token_auth_create_users.rb +54 -0
  47. data/db/migrate/20180622142754_add_allow_change_password_to_users.rb +5 -0
  48. data/db/migrate/20191217010224_add_approved_to_users.rb +7 -0
  49. data/db/migrate/20200503182019_change_tokens_to_json_b.rb +9 -0
  50. data/lib/commands/rhino_command.rb +59 -0
  51. data/lib/generators/rhino/dev/setup/setup_generator.rb +175 -0
  52. data/lib/generators/rhino/dev/setup/templates/env.client.tt +4 -0
  53. data/lib/generators/rhino/dev/setup/templates/env.server.tt +35 -0
  54. data/lib/generators/rhino/dev/setup/templates/prepare-commit-msg +17 -0
  55. data/lib/generators/rhino/install/install_generator.rb +24 -0
  56. data/lib/generators/rhino/install/templates/account.rb +4 -0
  57. data/lib/generators/rhino/install/templates/rhino.rb +24 -0
  58. data/lib/generators/rhino/install/templates/user.rb +4 -0
  59. data/lib/generators/rhino/module/module_generator.rb +93 -0
  60. data/lib/generators/rhino/module/templates/engine.rb.tt +19 -0
  61. data/lib/generators/rhino/module/templates/install_generator.rb.tt +12 -0
  62. data/lib/generators/rhino/module/templates/module_tasks.rake.tt +17 -0
  63. data/lib/generators/rhino/policy/policy_generator.rb +33 -0
  64. data/lib/generators/rhino/policy/templates/policy.rb.tt +46 -0
  65. data/lib/generators/test_unit/rhino_policy_generator.rb +13 -0
  66. data/lib/generators/test_unit/templates/policy_test.rb.tt +57 -0
  67. data/lib/rhino/engine.rb +140 -0
  68. data/lib/rhino/omniauth/strategies/azure_o_auth2.rb +16 -0
  69. data/lib/rhino/resource/active_model_extension/backing_store/google_sheet.rb +89 -0
  70. data/lib/rhino/resource/active_model_extension/backing_store.rb +33 -0
  71. data/lib/rhino/resource/active_model_extension/describe.rb +38 -0
  72. data/lib/rhino/resource/active_model_extension/params.rb +70 -0
  73. data/lib/rhino/resource/active_model_extension/properties.rb +229 -0
  74. data/lib/rhino/resource/active_model_extension/reference.rb +50 -0
  75. data/lib/rhino/resource/active_model_extension/routing.rb +15 -0
  76. data/lib/rhino/resource/active_model_extension/serialization.rb +16 -0
  77. data/lib/rhino/resource/active_model_extension.rb +38 -0
  78. data/lib/rhino/resource/active_record_extension/describe.rb +44 -0
  79. data/lib/rhino/resource/active_record_extension/params.rb +213 -0
  80. data/lib/rhino/resource/active_record_extension/properties.rb +85 -0
  81. data/lib/rhino/resource/active_record_extension/properties_describe.rb +226 -0
  82. data/lib/rhino/resource/active_record_extension/reference.rb +50 -0
  83. data/lib/rhino/resource/active_record_extension/routing.rb +21 -0
  84. data/lib/rhino/resource/active_record_extension/search.rb +23 -0
  85. data/lib/rhino/resource/active_record_extension/serialization.rb +16 -0
  86. data/lib/rhino/resource/active_record_extension.rb +30 -0
  87. data/lib/rhino/resource/active_record_tree.rb +50 -0
  88. data/lib/rhino/resource/active_storage_extension.rb +41 -0
  89. data/lib/rhino/resource/describe.rb +19 -0
  90. data/lib/rhino/resource/owner.rb +172 -0
  91. data/lib/rhino/resource/params.rb +31 -0
  92. data/lib/rhino/resource/properties.rb +192 -0
  93. data/lib/rhino/resource/reference.rb +31 -0
  94. data/lib/rhino/resource/routing.rb +107 -0
  95. data/lib/rhino/resource/serialization.rb +13 -0
  96. data/lib/rhino/resource/sieves.rb +36 -0
  97. data/lib/rhino/resource.rb +54 -0
  98. data/lib/rhino/sieve/filter.rb +149 -0
  99. data/lib/rhino/sieve/helpers.rb +11 -0
  100. data/lib/rhino/sieve/limit.rb +20 -0
  101. data/lib/rhino/sieve/offset.rb +16 -0
  102. data/lib/rhino/sieve/order.rb +143 -0
  103. data/lib/rhino/sieve/search.rb +20 -0
  104. data/lib/rhino/sieve.rb +158 -0
  105. data/lib/rhino/test_case/controller.rb +134 -0
  106. data/lib/rhino/test_case/override.rb +19 -0
  107. data/lib/rhino/test_case/policy.rb +76 -0
  108. data/lib/rhino/test_case.rb +10 -0
  109. data/lib/rhino/version.rb +17 -0
  110. data/lib/rhino.rb +129 -0
  111. data/lib/tasks/rhino.rake +38 -0
  112. data/lib/tasks/rhino_dev.rake +17 -0
  113. data/lib/validators/country_validator.rb +11 -0
  114. data/lib/validators/email_validator.rb +8 -0
  115. data/lib/validators/ipv4_validator.rb +10 -0
  116. data/lib/validators/mac_address_validator.rb +9 -0
  117. metadata +178 -0
@@ -0,0 +1,158 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rhino
4
+ module Sieve
5
+ extend ActiveSupport::Autoload
6
+
7
+ autoload :Filter
8
+ autoload :Limit
9
+ autoload :Offset
10
+ autoload :Order
11
+ autoload :Search
12
+ autoload :Helpers
13
+ end
14
+
15
+ class SieveStack
16
+ class Sieve
17
+ attr_reader :args, :block, :klass
18
+
19
+ def initialize(klass, args, block)
20
+ @klass = klass
21
+ @args = args
22
+ @block = block
23
+ end
24
+
25
+ delegate :name, to: :klass
26
+
27
+ def ==(other)
28
+ case other
29
+ when Sieve
30
+ klass == other.klass
31
+ when Class
32
+ klass == other
33
+ end
34
+ end
35
+
36
+ def inspect
37
+ if klass.is_a?(Class)
38
+ klass.to_s
39
+ else
40
+ klass.class.to_s
41
+ end
42
+ end
43
+
44
+ def build(app)
45
+ klass.new(app, *args, &block)
46
+ end
47
+
48
+ def build_instrumented(app)
49
+ InstrumentationProxy.new(build(app), inspect)
50
+ end
51
+ end
52
+
53
+ # This class is used to instrument the execution of a single sieve.
54
+ # It proxies the `s` method transparently and instruments the method
55
+ # call.
56
+ class InstrumentationProxy
57
+ EVENT_NAME = 'rhino.apply_sieve'
58
+
59
+ def initialize(sieve, class_name)
60
+ @sieve = sieve
61
+
62
+ @payload = {
63
+ sieve: class_name
64
+ }
65
+ end
66
+
67
+ def resolve(scope, params)
68
+ ActiveSupport::Notifications.instrument(EVENT_NAME, @payload) do
69
+ @sieve.resolve(scope, params)
70
+ end
71
+ end
72
+ end
73
+
74
+ # Pretty much stolen from ActionDispatch::MiddlewareStack
75
+ include Enumerable
76
+
77
+ attr_accessor :sieves
78
+
79
+ def initialize(*_args)
80
+ @sieves = []
81
+ yield(self) if block_given?
82
+ end
83
+
84
+ # rubocop:todo Style/ExplicitBlockArgument
85
+ def each
86
+ @sieves.each { |x| yield x }
87
+ end
88
+ # rubocop:enable Style/ExplicitBlockArgument
89
+
90
+ delegate :[], :size, :last, to: :sieves
91
+
92
+ def unshift(klass, *args, &block)
93
+ sieves.unshift(build_sieve(klass, args, block))
94
+ end
95
+ ruby2_keywords(:unshift) if respond_to?(:ruby2_keywords, true)
96
+
97
+ def initialize_copy(other)
98
+ self.sieves = other.sieves.dup
99
+ end
100
+
101
+ def insert(index, klass, *args, &block)
102
+ index = assert_index(index, :before)
103
+ sieves.insert(index, build_sieve(klass, args, block))
104
+ end
105
+ ruby2_keywords(:insert) if respond_to?(:ruby2_keywords, true)
106
+
107
+ alias insert_before insert
108
+
109
+ def insert_after(index, *args, &block)
110
+ index = assert_index(index, :after)
111
+ insert(index + 1, *args, &block)
112
+ end
113
+ ruby2_keywords(:insert_after) if respond_to?(:ruby2_keywords, true)
114
+
115
+ def swap(target, *args, &block)
116
+ index = assert_index(target, :before)
117
+ insert(index, *args, &block)
118
+ sieves.delete_at(index + 1)
119
+ end
120
+ ruby2_keywords(:swap) if respond_to?(:ruby2_keywords, true)
121
+
122
+ def delete(target)
123
+ sieves.delete_if { |m| m.klass == target }
124
+ end
125
+
126
+ def use(klass, *args, &block)
127
+ sieves.push(build_sieve(klass, args, block))
128
+ end
129
+ ruby2_keywords(:use) if respond_to?(:ruby2_keywords, true)
130
+
131
+ def build(app = nil, &block)
132
+ instrumenting = ActiveSupport::Notifications.notifier.listening?(InstrumentationProxy::EVENT_NAME)
133
+
134
+ # Freeze only production so that reloading works
135
+ sieves.freeze unless Rails.env.development?
136
+
137
+ sieves.reverse.inject(app || block) do |a, e|
138
+ if instrumenting
139
+ e.build_instrumented(a)
140
+ else
141
+ e.build(a)
142
+ end
143
+ end
144
+ end
145
+
146
+ private
147
+ def assert_index(index, where)
148
+ i = index.is_a?(Integer) ? index : sieves.index { |m| m.klass == index }
149
+ raise "No such sieve to insert #{where}: #{index.inspect}" unless i
150
+
151
+ i
152
+ end
153
+
154
+ def build_sieve(klass, args, block)
155
+ Sieve.new(klass, args, block)
156
+ end
157
+ end
158
+ end
@@ -0,0 +1,134 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rhino
4
+ module TestCase
5
+ class ControllerTest < ActionDispatch::IntegrationTest
6
+ def setup
7
+ DeviseTokenAuth.cookie_attributes[:secure] = false
8
+ DeviseTokenAuth.cookie_attributes[:domain] = :all
9
+ end
10
+
11
+ protected
12
+ def users_index(params: {}, headers: {})
13
+ get "/api/users", params:, headers:
14
+ end
15
+
16
+ def validate_session(params: {}, headers: {})
17
+ get "/api/auth/validate_token", params:, headers:
18
+ end
19
+
20
+ def sign_out_with_empty_auth_cookie
21
+ params = {}
22
+ headers = { "Cookie" => "auth_cookie=;" }
23
+ delete "/api/auth/sign_out", params:, headers:
24
+ end
25
+
26
+ def sign_out(params: {}, headers: {})
27
+ delete "/api/auth/sign_out", params:, headers:
28
+ end
29
+
30
+ def get_api(path, params: {}, headers: {})
31
+ get path, params:, headers:, xhr: true, as: :json
32
+ end
33
+
34
+ def post_api(path, params: {})
35
+ post path, params:, xhr: true, as: :json
36
+ end
37
+
38
+ def patch_api(path, params: {})
39
+ patch path, params:, xhr: true, as: :json
40
+ end
41
+
42
+ def put_api(path, params: {})
43
+ put path, params:, xhr: true, as: :json
44
+ end
45
+
46
+ def delete_api(path)
47
+ delete path, xhr: true, as: :json
48
+ end
49
+
50
+ def head_api(path)
51
+ head path, xhr: true, as: :json
52
+ end
53
+
54
+ def assert_response_ok
55
+ assert_equal 200, response.status
56
+ end
57
+
58
+ def assert_response_bad_request
59
+ assert_equal 400, response.status
60
+ end
61
+
62
+ def assert_response_unauthorized
63
+ assert_equal 401, response.status
64
+ end
65
+
66
+ def assert_response_forbidden
67
+ assert_equal 403, response.status
68
+ end
69
+
70
+ def assert_response_not_found
71
+ assert_equal 404, response.status
72
+ end
73
+
74
+ def assert_response_unprocessable
75
+ assert_equal 422, response.status
76
+ end
77
+
78
+ def assert_deleted_cookie(cookie_name)
79
+ assert response.cookies.key?(cookie_name)
80
+ assert_nil response.cookies[cookie_name]
81
+ end
82
+
83
+ def assert_not_deleted_cookie(cookie_name)
84
+ has_cookie = response.cookies.key?(cookie_name)
85
+ cookie_not_blank = response.cookies[cookie_name].present?
86
+ assert !has_cookie || cookie_not_blank, "Response should either not have the auth cookie present or it should be set to something. Current value is #{response.cookies[cookie_name]}" # rubocop:disable Layout/LineLength
87
+ end
88
+
89
+ def sign_in(user = nil)
90
+ @current_user = user || create(:user)
91
+ post "/api/auth/sign_in", params: {
92
+ email: @current_user.email,
93
+ password: @current_user.password
94
+ }, as: :json
95
+ assert_response_ok
96
+ assert response.cookies[DeviseTokenAuth.cookie_name], "Auth cookie #{DeviseTokenAuth.cookie_name} not present"
97
+ end
98
+
99
+ def parsed_response
100
+ JSON.parse response.body
101
+ end
102
+
103
+ def parsed_auth_cookie_header
104
+ JSON.parse(cookies[DeviseTokenAuth.cookie_name])
105
+ end
106
+
107
+ def empty_auth_cookie_header
108
+ { "Cookie" => "auth_cookie=;" }
109
+ end
110
+
111
+ def current_access_token
112
+ parsed_auth_cookie["access-token"]
113
+ end
114
+
115
+ def delete_user_tokens(user = nil)
116
+ (user || @current_user).reload.update tokens: {}
117
+ end
118
+
119
+ def serialized_user(user = @current_user)
120
+ {
121
+ "id" => user.id,
122
+ "provider" => "email",
123
+ "uid" => user.email,
124
+ "name" => user.name,
125
+ "nickname" => user.nickname,
126
+ "image" => user.image,
127
+ "email" => user.email,
128
+ "allow_password_change" => false,
129
+ "approved" => true
130
+ }
131
+ end
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rhino
4
+ module TestHelperOverride
5
+ def assert_overridden(overriding, klass, *methods)
6
+ methods.each do |method|
7
+ assert_equal File.realpath(overriding), klass.new.method(method).source_location[0]
8
+ end
9
+ end
10
+ end
11
+ end
12
+
13
+ module Rhino
14
+ module TestCase
15
+ class Override < ActiveSupport::TestCase
16
+ include Rhino::TestHelperOverride
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Based on https://github.com/varvet/pundit/issues/204#issuecomment-60166450 and
4
+ # https://github.com/varvet/pundit/issues/204#issuecomment-192256023
5
+
6
+ module Rhino
7
+ module TestHelperPolicy
8
+ def assert_permit(user, record, action)
9
+ msg = "User #{user.inspect} should be permitted to #{action} #{record}, but isn't permitted"
10
+ assert permit(user, record, action), msg
11
+ end
12
+
13
+ def assert_not_permit(user, record, action)
14
+ msg = "User #{user.inspect} should NOT be permitted to #{action} #{record}, but is permitted"
15
+ assert_not permit(user, record, action), msg
16
+ end
17
+
18
+ def assert_scope_only(user, scope, expected)
19
+ resolved = scope(user, scope)
20
+ msg = "User #{user.inspect} should be scoped to #{expected.inspect}, but receives #{resolved.inspect}"
21
+ assert_empty resolved.to_a.difference(expected), msg
22
+ assert_empty expected.difference(resolved.to_a), msg
23
+ end
24
+
25
+ def assert_params(user, record, action, expected)
26
+ permitted_params = params(user, record, action)
27
+ msg = "User #{user.inspect} should be permitted #{action} params for #{record}, but isn't"
28
+ assert_empty permitted_params.to_a.difference(expected), msg
29
+ assert_empty expected.difference(permitted_params.to_a), msg
30
+ end
31
+
32
+ def assert_scope_empty(user, scope)
33
+ resolved = scope(user, scope)
34
+ msg = "User #{user.inspect} should have no scope, but receives #{resolved.inspect}"
35
+ assert_equal 0, resolved.count, msg
36
+ end
37
+
38
+ def policy_test_name
39
+ self.class.ancestors.find { |a| a.to_s.end_with?("PolicyTest") }
40
+ end
41
+
42
+ def policy_instance(user, record)
43
+ klass = policy_test_name.to_s.gsub(/Test/, "")
44
+ klass.constantize.new(user, record)
45
+ end
46
+
47
+ def policy_scope_instance(user, scope)
48
+ klass = policy_test_name.to_s.gsub(/Test/, "::Scope")
49
+ klass.constantize.new(user, scope)
50
+ end
51
+
52
+ def scope(user, scope)
53
+ policy_scope_instance(user, scope).resolve
54
+ end
55
+
56
+ def permit(user, record, action)
57
+ policy_instance(user, record).public_send("#{action}?")
58
+ end
59
+
60
+ def params(user, record, action)
61
+ policy_instance(user, record).public_send("permitted_attributes_for_#{action}")
62
+ end
63
+ end
64
+ end
65
+
66
+ module Rhino
67
+ module TestCase
68
+ class Policy < ActiveSupport::TestCase
69
+ include Rhino::TestHelperPolicy
70
+
71
+ def self.testing_policy
72
+ name.gsub(/Test/, "::Scope")
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "test_case/controller"
4
+ require_relative "test_case/override"
5
+ require_relative "test_case/policy"
6
+
7
+ module Rhino
8
+ module TestCase
9
+ end
10
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rhino
4
+ # Returns the currently loaded version of Rhino core as a +Gem::Version+.
5
+ def self.gem_version
6
+ Gem::Version.new VERSION::STRING
7
+ end
8
+
9
+ module VERSION
10
+ MAJOR = 0
11
+ MINOR = 20
12
+ TINY = 0
13
+ PRE = "beta.18"
14
+
15
+ STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
16
+ end
17
+ end
data/lib/rhino.rb ADDED
@@ -0,0 +1,129 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rhino/engine'
4
+ require 'validators/country_validator'
5
+ require 'validators/email_validator'
6
+ require 'validators/ipv4_validator'
7
+ require 'validators/mac_address_validator'
8
+ require 'active_support'
9
+
10
+ module Rhino
11
+ extend ActiveSupport::Autoload
12
+ extend ActiveSupport::Concern
13
+
14
+ autoload :SieveStack, 'rhino/sieve'
15
+
16
+ # The root path for the api ie '/api'
17
+ mattr_accessor :namespace, default: :api
18
+
19
+ # Include Rhino::Resource::ActiveRecordExtension by default
20
+ mattr_accessor :auto_include_active_record, default: true
21
+
22
+ mattr_accessor :resources, default: if Rails.env.production?
23
+ ['ActiveStorage::Attachment']
24
+ else
25
+ ['ActiveStorage::Attachment', 'Rhino::OpenApiInfo', 'Rhino::InfoGraph']
26
+ end
27
+
28
+ mattr_accessor :resource_classes
29
+
30
+ mattr_accessor :registered_modules, default: {}
31
+
32
+ # Whether to allow signup or not
33
+ mattr_accessor :allow_signup, default: true
34
+
35
+ # sieves
36
+ mattr_accessor :sieves
37
+
38
+ ##
39
+ # Stolen from devise
40
+ #
41
+ # https://github.com/heartcombo/devise/blob/main/lib/devise.rb#L310
42
+ class Getter
43
+ def initialize(name)
44
+ @name = name
45
+ end
46
+
47
+ def get
48
+ @name.constantize
49
+ end
50
+ end
51
+
52
+ def self.ref(arg)
53
+ Getter.new(arg)
54
+ end
55
+ #
56
+ ##
57
+
58
+ # Get the resource classes from the resource reference object.
59
+ def self.resource_classes
60
+ resource_classes ||= resources.map(&:constantize)
61
+
62
+ resource_classes
63
+ end
64
+
65
+ self.sieves = Rhino::SieveStack.new do |sieve|
66
+ sieve.use Rhino::Sieve::Filter
67
+
68
+ sieve.use Rhino::Sieve::Search
69
+
70
+ sieve.use Rhino::Sieve::Order
71
+
72
+ sieve.use Rhino::Sieve::Offset
73
+
74
+ sieve.use Rhino::Sieve::Limit, default_limit: 20
75
+ end
76
+
77
+ def self.auth_owner
78
+ @@auth_owner_ref.get
79
+ end
80
+
81
+ # Set the auth owner reference object to access the mailer.
82
+ def self.auth_owner=(class_name)
83
+ @@auth_owner_ref = ref(class_name) # rubocop:disable Style/ClassVars
84
+ end
85
+ self.auth_owner = 'User'
86
+
87
+ def self.auth_owner_sym
88
+ auth_owner.to_s.underscore.pluralize.to_sym
89
+ end
90
+
91
+ def self.base_owner
92
+ @@base_owner_ref.get
93
+ end
94
+
95
+ # Set the mailer reference object to access the mailer.
96
+ def self.base_owner=(class_name)
97
+ @@base_owner_ref = ref(class_name) # rubocop:disable Style/ClassVars
98
+ end
99
+ self.base_owner = 'User'
100
+
101
+ def self.base_owner_sym
102
+ base_owner.to_s.underscore.pluralize.to_sym
103
+ end
104
+
105
+ def self.same_owner?
106
+ base_owner == auth_owner
107
+ end
108
+
109
+ def self.base_to_auth
110
+ return auth_owner.model_name.i18n_key if same_owner?
111
+
112
+ return auth_owner_sym if base_owner.reflections.key?(auth_owner_sym.to_s)
113
+
114
+ nil
115
+ end
116
+
117
+ def self.auth_to_base
118
+ return auth_owner.model_name.i18n_key if same_owner?
119
+
120
+ return base_owner_sym if auth_owner.reflections.key?(base_owner_sym.to_s)
121
+
122
+ nil
123
+ end
124
+
125
+ # Default way to set up Rhino
126
+ def self.setup
127
+ yield self
128
+ end
129
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ namespace :rhino do
4
+ # Prevent migration installation task from showing up twice.
5
+ Rake::Task['rhino_engine:install:migrations'].clear_comments
6
+
7
+ desc 'Install Rhino'
8
+ task install: %w[environment run_installer]
9
+
10
+ desc 'Generate Rhino module'
11
+ task module: %w[environment run_module]
12
+
13
+ desc 'Generate fulle Rhino module'
14
+ task module_full: %w[environment run_module_full]
15
+
16
+ desc 'Export Rhino Open API information for client'
17
+ task open_api_export: :environment do
18
+ static_file = Rails.root.parent.join('client', 'src', 'models', 'static.js')
19
+ File.open(static_file, 'w') do |f|
20
+ f.write "const api = #{Rhino::OpenApiInfo.index};\n\n"
21
+ f.write("export default api;\n")
22
+ end
23
+ end
24
+
25
+ task run_installer: :environment do
26
+ Rake::Task['rhino_engine:install:migrations'].invoke if Rake::Task.task_defined?('rhino_engine:install:migrations')
27
+
28
+ Rails::Command.invoke :generate, ['rhino:install']
29
+ end
30
+
31
+ task run_module: :environment do
32
+ Rails::Command.invoke :generate, ['rhino:module']
33
+ end
34
+
35
+ task run_module_full: :environment do
36
+ Rails::Command.invoke :generate, ['rhino:module', '--full']
37
+ end
38
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ namespace :rhino do
4
+ namespace :dev do
5
+ desc "Setup Rhino development environment"
6
+ task setup: :environment do
7
+ # Extract extra arguments from ARGV
8
+ extra_args = ARGV.drop_while { |arg| arg != "--" }.drop(1)
9
+
10
+ # Remove extra arguments from ARGV to prevent interference with other tasks
11
+ extra_args.each { |arg| ARGV.delete(arg) }
12
+ ARGV.delete("--")
13
+
14
+ Rails::Command.invoke :generate, ["rhino:dev:setup", *extra_args]
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ class CountryValidator < ActiveModel::EachValidator
4
+ def validate_each(record, attribute, value)
5
+ if options[:alpha3]
6
+ record.errors.add(attribute, "must be 3 characters (ISO 3166-1).") unless ISO3166::Country.find_country_by_alpha3(value)
7
+ else
8
+ record.errors.add(attribute, "must be 2 characters (ISO 3166-1).") unless ISO3166::Country[value]
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ class EmailValidator < ActiveModel::EachValidator
4
+ def validate_each(record, attribute, value)
5
+ # https://stackoverflow.com/questions/38611405/email-validation-in-ruby-on-rails
6
+ record.errors.add(attribute, "must be a valid email address") unless value&.match?(Devise.email_regexp)
7
+ end
8
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "resolv"
4
+
5
+ class Ipv4Validator < ActiveModel::EachValidator
6
+ def validate_each(record, attribute, value)
7
+ # https://gist.github.com/mozamimy/52c0004c8370f78df2c2
8
+ record.errors.add(attribute, "not a valid IPv4 address") unless Resolv::IPv4::Regex.match?(value)
9
+ end
10
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "resolv"
4
+
5
+ class MacAddressValidator < ActiveModel::EachValidator
6
+ def validate_each(record, attribute, value)
7
+ record.errors.add(attribute, "not a valid Mac address") unless /\A([0-9A-Fa-f]{2}[-:]){5}([0-9A-Fa-f]{2})\z/.match?(value)
8
+ end
9
+ end