rhino_project_core 0.20.0.alpha.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (134) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.md +28 -0
  4. data/Rakefile +35 -0
  5. data/app/assets/stripe_flow.png +0 -0
  6. data/app/controllers/concerns/rhino/authenticated.rb +18 -0
  7. data/app/controllers/concerns/rhino/error_handling.rb +60 -0
  8. data/app/controllers/concerns/rhino/paper_trail_whodunnit.rb +11 -0
  9. data/app/controllers/concerns/rhino/permit.rb +38 -0
  10. data/app/controllers/concerns/rhino/set_current_user.rb +13 -0
  11. data/app/controllers/rhino/account_controller.rb +34 -0
  12. data/app/controllers/rhino/active_model_extension_controller.rb +52 -0
  13. data/app/controllers/rhino/base_controller.rb +23 -0
  14. data/app/controllers/rhino/crud_controller.rb +57 -0
  15. data/app/controllers/rhino/simple_controller.rb +13 -0
  16. data/app/controllers/rhino/simple_stream_controller.rb +12 -0
  17. data/app/helpers/rhino/omniauth_helper.rb +67 -0
  18. data/app/helpers/rhino/policy_helper.rb +42 -0
  19. data/app/helpers/rhino/segment_helper.rb +62 -0
  20. data/app/models/rhino/account.rb +13 -0
  21. data/app/models/rhino/current.rb +7 -0
  22. data/app/models/rhino/user.rb +44 -0
  23. data/app/overrides/active_record/autosave_association_override.rb +18 -0
  24. data/app/overrides/active_record/delegated_type_override.rb +14 -0
  25. data/app/overrides/activestorage/direct_uploads_controller_override.rb +23 -0
  26. data/app/overrides/activestorage/redirect_controller_override.rb +21 -0
  27. data/app/overrides/activestorage/redirect_representation_controller_override.rb +21 -0
  28. data/app/overrides/devise_token_auth/confirmations_controller_override.rb +14 -0
  29. data/app/overrides/devise_token_auth/omniauth_callbacks_controller_override.rb +45 -0
  30. data/app/overrides/devise_token_auth/passwords_controller_override.rb +9 -0
  31. data/app/overrides/devise_token_auth/registrations_controller_override.rb +20 -0
  32. data/app/overrides/devise_token_auth/sessions_controller_override.rb +26 -0
  33. data/app/overrides/devise_token_auth/token_validations_controller_override.rb +18 -0
  34. data/app/policies/rhino/account_policy.rb +27 -0
  35. data/app/policies/rhino/active_storage_attachment_policy.rb +16 -0
  36. data/app/policies/rhino/admin_policy.rb +20 -0
  37. data/app/policies/rhino/base_policy.rb +72 -0
  38. data/app/policies/rhino/crud_policy.rb +109 -0
  39. data/app/policies/rhino/editor_policy.rb +12 -0
  40. data/app/policies/rhino/global_policy.rb +8 -0
  41. data/app/policies/rhino/resource_info_policy.rb +9 -0
  42. data/app/policies/rhino/user_policy.rb +20 -0
  43. data/app/policies/rhino/viewer_policy.rb +19 -0
  44. data/app/resources/rhino/info_graph.rb +41 -0
  45. data/app/resources/rhino/open_api_info.rb +108 -0
  46. data/config/routes.rb +19 -0
  47. data/db/migrate/20180101000000_devise_token_auth_create_users.rb +54 -0
  48. data/db/migrate/20180622142754_add_allow_change_password_to_users.rb +5 -0
  49. data/db/migrate/20191217010224_add_approved_to_users.rb +7 -0
  50. data/db/migrate/20200503182019_change_tokens_to_json_b.rb +9 -0
  51. data/lib/commands/rhino/module/coverage_command.rb +44 -0
  52. data/lib/commands/rhino/module/dummy_command.rb +43 -0
  53. data/lib/commands/rhino/module/new_command.rb +34 -0
  54. data/lib/commands/rhino/module/rails_command.rb +43 -0
  55. data/lib/commands/rhino/module/test_command.rb +43 -0
  56. data/lib/generators/rhino/dev/setup/setup_generator.rb +199 -0
  57. data/lib/generators/rhino/dev/setup/templates/env.client.tt +4 -0
  58. data/lib/generators/rhino/dev/setup/templates/env.root.tt +1 -0
  59. data/lib/generators/rhino/dev/setup/templates/env.server.tt +35 -0
  60. data/lib/generators/rhino/dev/setup/templates/prepare-commit-msg +17 -0
  61. data/lib/generators/rhino/install/install_generator.rb +24 -0
  62. data/lib/generators/rhino/install/templates/account.rb +4 -0
  63. data/lib/generators/rhino/install/templates/rhino.rb +24 -0
  64. data/lib/generators/rhino/install/templates/user.rb +4 -0
  65. data/lib/generators/rhino/model/model_generator.rb +96 -0
  66. data/lib/generators/rhino/module/USAGE +6 -0
  67. data/lib/generators/rhino/module/module_generator.rb +92 -0
  68. data/lib/generators/rhino/module/templates/%name%.gemspec.tt +24 -0
  69. data/lib/generators/rhino/module/templates/lib/%namespaced_name%/engine.rb.tt +18 -0
  70. data/lib/generators/rhino/module/templates/lib/generators/%namespaced_name%/install/install_generator.rb.tt +12 -0
  71. data/lib/generators/rhino/module/templates/lib/tasks/%namespaced_name%_tasks.rake.tt +13 -0
  72. data/lib/generators/rhino/module/templates/test/dummy/app/models/user.rb +4 -0
  73. data/lib/generators/rhino/module/templates/test/dummy/config/database.yml +25 -0
  74. data/lib/generators/rhino/module/templates/test/dummy/config/initializers/devise.rb +311 -0
  75. data/lib/generators/rhino/module/templates/test/dummy/config/initializers/devise_token_auth.rb +71 -0
  76. data/lib/generators/rhino/module/templates/test/test_helper.rb +54 -0
  77. data/lib/generators/rhino/policy/policy_generator.rb +33 -0
  78. data/lib/generators/rhino/policy/templates/policy.rb.tt +46 -0
  79. data/lib/generators/test_unit/rhino_policy_generator.rb +13 -0
  80. data/lib/generators/test_unit/templates/policy_test.rb.tt +57 -0
  81. data/lib/rhino/engine.rb +166 -0
  82. data/lib/rhino/omniauth/strategies/azure_o_auth2.rb +16 -0
  83. data/lib/rhino/resource/active_model_extension/backing_store/google_sheet.rb +89 -0
  84. data/lib/rhino/resource/active_model_extension/backing_store.rb +33 -0
  85. data/lib/rhino/resource/active_model_extension/describe.rb +38 -0
  86. data/lib/rhino/resource/active_model_extension/params.rb +70 -0
  87. data/lib/rhino/resource/active_model_extension/properties.rb +231 -0
  88. data/lib/rhino/resource/active_model_extension/reference.rb +50 -0
  89. data/lib/rhino/resource/active_model_extension/routing.rb +15 -0
  90. data/lib/rhino/resource/active_model_extension/serialization.rb +16 -0
  91. data/lib/rhino/resource/active_model_extension.rb +38 -0
  92. data/lib/rhino/resource/active_record_extension/describe.rb +44 -0
  93. data/lib/rhino/resource/active_record_extension/params.rb +213 -0
  94. data/lib/rhino/resource/active_record_extension/properties.rb +85 -0
  95. data/lib/rhino/resource/active_record_extension/properties_describe.rb +228 -0
  96. data/lib/rhino/resource/active_record_extension/reference.rb +50 -0
  97. data/lib/rhino/resource/active_record_extension/routing.rb +21 -0
  98. data/lib/rhino/resource/active_record_extension/search.rb +23 -0
  99. data/lib/rhino/resource/active_record_extension/serialization.rb +16 -0
  100. data/lib/rhino/resource/active_record_extension/super_admin.rb +25 -0
  101. data/lib/rhino/resource/active_record_extension.rb +32 -0
  102. data/lib/rhino/resource/active_record_tree.rb +50 -0
  103. data/lib/rhino/resource/active_storage_extension.rb +41 -0
  104. data/lib/rhino/resource/describe.rb +19 -0
  105. data/lib/rhino/resource/owner.rb +172 -0
  106. data/lib/rhino/resource/params.rb +31 -0
  107. data/lib/rhino/resource/properties.rb +192 -0
  108. data/lib/rhino/resource/reference.rb +29 -0
  109. data/lib/rhino/resource/routing.rb +107 -0
  110. data/lib/rhino/resource/serialization.rb +13 -0
  111. data/lib/rhino/resource/sieves.rb +36 -0
  112. data/lib/rhino/resource.rb +55 -0
  113. data/lib/rhino/sieve/filter.rb +149 -0
  114. data/lib/rhino/sieve/geospatial.rb +45 -0
  115. data/lib/rhino/sieve/helpers.rb +11 -0
  116. data/lib/rhino/sieve/limit.rb +20 -0
  117. data/lib/rhino/sieve/offset.rb +16 -0
  118. data/lib/rhino/sieve/order.rb +143 -0
  119. data/lib/rhino/sieve/search.rb +20 -0
  120. data/lib/rhino/sieve.rb +159 -0
  121. data/lib/rhino/test_case/controller.rb +145 -0
  122. data/lib/rhino/test_case/model.rb +86 -0
  123. data/lib/rhino/test_case/override.rb +19 -0
  124. data/lib/rhino/test_case/policy.rb +76 -0
  125. data/lib/rhino/test_case.rb +11 -0
  126. data/lib/rhino/version.rb +17 -0
  127. data/lib/rhino_project_core.rb +131 -0
  128. data/lib/tasks/rhino.rake +24 -0
  129. data/lib/tasks/rhino_dev.rake +17 -0
  130. data/lib/validators/country_validator.rb +11 -0
  131. data/lib/validators/email_validator.rb +8 -0
  132. data/lib/validators/ipv4_validator.rb +10 -0
  133. data/lib/validators/mac_address_validator.rb +9 -0
  134. metadata +531 -0
@@ -0,0 +1,145 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rhino
4
+ module TestCase
5
+ # rubocop:disable Metrics/ClassLength
6
+ class ControllerTest < ActionDispatch::IntegrationTest
7
+ setup :set_cookie_attributes
8
+
9
+ protected
10
+ def users_index(params: {}, headers: {})
11
+ get "/api/users", params:, headers:
12
+ end
13
+
14
+ def validate_session(params: {}, headers: {})
15
+ get "/api/auth/validate_token", params:, headers:
16
+ end
17
+
18
+ def sign_out_with_empty_auth_cookie
19
+ params = {}
20
+ headers = { "Cookie" => "#{DeviseTokenAuth.cookie_name}=;" }
21
+ delete "/api/auth/sign_out", params:, headers:
22
+ end
23
+
24
+ def sign_out(params: {}, headers: {})
25
+ delete "/api/auth/sign_out", params:, headers:
26
+ end
27
+
28
+ def get_api(path, params: {}, headers: {})
29
+ get path, params:, headers:, xhr: true, as: :json
30
+ end
31
+
32
+ def post_api(path, params: {})
33
+ post path, params:, xhr: true, as: :json
34
+ end
35
+
36
+ def patch_api(path, params: {}, headers: {})
37
+ patch path, params:, xhr: true, as: :json, headers:
38
+ end
39
+
40
+ def put_api(path, params: {})
41
+ put path, params:, xhr: true, as: :json
42
+ end
43
+
44
+ def delete_api(path)
45
+ delete path, xhr: true, as: :json
46
+ end
47
+
48
+ def head_api(path)
49
+ head path, xhr: true, as: :json
50
+ end
51
+
52
+ def assert_response_ok
53
+ assert_equal 200, response.status
54
+ end
55
+
56
+ def assert_response_bad_request
57
+ assert_equal 400, response.status
58
+ end
59
+
60
+ def assert_response_unauthorized
61
+ assert_equal 401, response.status
62
+ end
63
+
64
+ def assert_response_forbidden
65
+ assert_equal 403, response.status
66
+ end
67
+
68
+ def assert_response_not_found
69
+ assert_equal 404, response.status
70
+ end
71
+
72
+ def assert_response_unprocessable
73
+ assert_equal 422, response.status
74
+ end
75
+
76
+ def assert_deleted_cookie(cookie_name)
77
+ assert response.cookies.key?(cookie_name)
78
+ assert_nil response.cookies[cookie_name]
79
+ end
80
+
81
+ def assert_not_deleted_cookie(cookie_name)
82
+ has_cookie = response.cookies.key?(cookie_name)
83
+ cookie_not_blank = response.cookies[cookie_name].present?
84
+ 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
85
+ end
86
+
87
+ def sign_in(user = nil)
88
+ @current_user = user || create(:user)
89
+ post "/api/auth/sign_in", params: {
90
+ email: @current_user.email,
91
+ password: @current_user.password
92
+ }, as: :json
93
+ assert_response_ok
94
+ headers = response.headers
95
+ # Use the configured header names
96
+ assert headers[DeviseTokenAuth.headers_names[:"access-token"]]
97
+ end
98
+
99
+ def parsed_response_ids
100
+ parsed_response.fetch("results", []).map { |result| result["id"] }
101
+ end
102
+
103
+ def parsed_response
104
+ JSON.parse response.body
105
+ end
106
+
107
+ def parsed_auth_cookie_header
108
+ JSON.parse(cookies[DeviseTokenAuth.cookie_name])
109
+ end
110
+
111
+ def empty_auth_cookie_header
112
+ { "Cookie" => "#{DeviseTokenAuth.cookie_name}=;" }
113
+ end
114
+
115
+ def current_access_token
116
+ parsed_auth_cookie["access-token"]
117
+ end
118
+
119
+ def delete_user_tokens(user = nil)
120
+ (user || @current_user).reload.update tokens: {}
121
+ end
122
+
123
+ def serialized_user(user = @current_user)
124
+ {
125
+ "id" => user.id,
126
+ "provider" => "email",
127
+ "uid" => user.email,
128
+ "name" => user.name,
129
+ "nickname" => user.nickname,
130
+ "image" => user.image,
131
+ "email" => user.email,
132
+ "allow_password_change" => false,
133
+ "approved" => true
134
+ }
135
+ end
136
+
137
+ private
138
+ def set_cookie_attributes
139
+ DeviseTokenAuth.cookie_attributes[:secure] = false
140
+ DeviseTokenAuth.cookie_attributes[:domain] = :all
141
+ end
142
+ end
143
+ # rubocop:enable Metrics/ClassLength
144
+ end
145
+ end
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rhino
4
+ module TestCase
5
+ class ModelTest < ActiveSupport::TestCase
6
+ protected
7
+ def assert_required(attribute)
8
+ instance = @model.new
9
+ instance[attribute] = nil
10
+ assert instance.invalid?
11
+ assert_includes instance.errors.messages[attribute], "can't be blank", ":#{attribute} should be required"
12
+ end
13
+
14
+ def assert_not_required(attribute)
15
+ instance = build factory
16
+ instance[attribute] = nil
17
+ assert instance.valid?, ":#{attribute} should not be required, got errors: #{instance.errors.full_messages}"
18
+ end
19
+
20
+ def assert_association_required(association)
21
+ instance = @model.new
22
+ instance.send("#{association}_id=", nil)
23
+ assert instance.invalid?
24
+ assert_includes instance.errors.messages[association], "must exist", ":#{association} should be required"
25
+ end
26
+
27
+ def assert_uniqueness(attribute, scope: nil)
28
+ instance = create factory
29
+ duplicate = build factory, attribute => instance[attribute]
30
+ duplicate.send("#{scope}=", instance.send(scope)) if scope.present?
31
+ assert duplicate.invalid?, "Should be invalid with duplicate #{attribute}"
32
+ assert_includes duplicate.errors.messages[attribute], "has already been taken", ":#{attribute} should be unique"
33
+ end
34
+
35
+ # association_model is a symbol
36
+ def assert_accepts_nested_attributes_for(association_model, association_factory: association_model) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/PerceivedComplexity
37
+ association = @model.reflect_on_association(association_model)
38
+
39
+ assert association.present?, "association :#{association_model} not found"
40
+
41
+ key = if association.macro == :belongs_to || association.macro == :has_one
42
+ "#{association_model}_attributes"
43
+ else
44
+ "#{association_model.to_s.pluralize}_attributes"
45
+ end
46
+
47
+ success_checker = if association.macro == :belongs_to || association.macro == :has_one
48
+ ->(instance) { instance.reload.send(association_model).present? }
49
+ else
50
+ ->(instance) { instance.reload.send(association_model).any? }
51
+ end
52
+
53
+ assert @model.new.respond_to?("#{key}="),
54
+ "#{@model} doesn't have a #{key} setter method. Probably there's no `accepts_nested_attributes_for :#{association_model}` in #{@model}" # rubocop:disable Layout/LineLength
55
+ object = if association_factory.is_a?(Proc)
56
+ model_instance = build factory
57
+ association_instance = association_factory.call model_instance
58
+ model_instance.send("#{key}=", association_instance.attributes)
59
+ model_instance
60
+ else
61
+ build factory, { key => build(association_factory).attributes }
62
+ end
63
+ assert object.valid?, "Should be valid after assigning nested model attributes, but got errors: #{object.errors.full_messages}"
64
+ assert object.save, "Should be able to save after assigning nested model attributes, but got errors: #{object.errors.full_messages}"
65
+ assert success_checker.call(object), "Should have a #{association_model} after assigning nested model attributes"
66
+ end
67
+
68
+ def assert_destroy_association_cascade(association_model, association_factory: association_model) # rubocop:disable Metrics/AbcSize
69
+ instance = create factory
70
+ association_instance = if association_factory.is_a?(Proc)
71
+ association_factory.call(instance)
72
+ else
73
+ create association, { @model.to_s.underscore => instance }
74
+ end
75
+ assert instance.reload.send(association_model).any?, "Should have a #{association_model} after creating #{@model}"
76
+ assert instance.destroy, "Should be able to destroy #{@model}"
77
+ assert_not association_instance.class.exists?(association_instance.id), "Should destroy #{association_model} when destroying #{@model}"
78
+ end
79
+
80
+ private
81
+ def factory
82
+ @factory ||= @model.to_s.underscore.to_sym
83
+ end
84
+ end
85
+ end
86
+ 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,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "test_case/controller"
4
+ require_relative "test_case/model"
5
+ require_relative "test_case/override"
6
+ require_relative "test_case/policy"
7
+
8
+ module Rhino
9
+ module TestCase
10
+ end
11
+ 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 = "alpha.0"
14
+
15
+ STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
16
+ end
17
+ end
@@ -0,0 +1,131 @@
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::Geospatial
69
+
70
+ sieve.use Rhino::Sieve::Search
71
+
72
+ sieve.use Rhino::Sieve::Order
73
+
74
+ sieve.use Rhino::Sieve::Offset
75
+
76
+ sieve.use Rhino::Sieve::Limit, default_limit: 20
77
+ end
78
+
79
+ def self.auth_owner
80
+ @@auth_owner_ref.get
81
+ end
82
+
83
+ # Set the auth owner reference object to access the mailer.
84
+ def self.auth_owner=(class_name)
85
+ @@auth_owner_ref = ref(class_name) # rubocop:disable Style/ClassVars
86
+ end
87
+ self.auth_owner = 'User'
88
+
89
+ def self.auth_owner_sym
90
+ auth_owner.to_s.underscore.pluralize.to_sym
91
+ end
92
+
93
+ def self.base_owner
94
+ @@base_owner_ref.get
95
+ end
96
+
97
+ # Set the mailer reference object to access the mailer.
98
+ def self.base_owner=(class_name)
99
+ @@base_owner_ref = ref(class_name) # rubocop:disable Style/ClassVars
100
+ end
101
+ self.base_owner = 'User'
102
+
103
+ def self.base_owner_sym
104
+ base_owner.to_s.underscore.pluralize.to_sym
105
+ end
106
+
107
+ def self.same_owner?
108
+ base_owner == auth_owner
109
+ end
110
+
111
+ def self.base_to_auth
112
+ return auth_owner.model_name.i18n_key if same_owner?
113
+
114
+ return auth_owner_sym if base_owner.reflections.key?(auth_owner_sym.to_s)
115
+
116
+ nil
117
+ end
118
+
119
+ def self.auth_to_base
120
+ return auth_owner.model_name.i18n_key if same_owner?
121
+
122
+ return base_owner_sym if auth_owner.reflections.key?(base_owner_sym.to_s)
123
+
124
+ nil
125
+ end
126
+
127
+ # Default way to set up Rhino
128
+ def self.setup
129
+ yield self
130
+ end
131
+ end
@@ -0,0 +1,24 @@
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 'Export Rhino Open API information for client'
11
+ task open_api_export: :environment do
12
+ static_file = Rails.root.parent.join('client', 'src', 'models', 'static.js')
13
+ File.open(static_file, 'w') do |f|
14
+ f.write "const api = #{Rhino::OpenApiInfo.index};\n\n"
15
+ f.write("export default api;\n")
16
+ end
17
+ end
18
+
19
+ task run_installer: :environment do
20
+ Rake::Task['rhino_engine:install:migrations'].invoke if Rake::Task.task_defined?('rhino_engine:install:migrations')
21
+
22
+ Rails::Command.invoke :generate, ['rhino:install']
23
+ end
24
+ 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