restme 0.0.37
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.
- checksums.yaml +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +8 -0
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +132 -0
- data/Dockerfile +28 -0
- data/LICENSE.txt +21 -0
- data/README.md +350 -0
- data/Rakefile +12 -0
- data/docker-compose.yml +13 -0
- data/lib/restme/authorize/rules.rb +35 -0
- data/lib/restme/create/rules.rb +110 -0
- data/lib/restme/restme.rb +30 -0
- data/lib/restme/scope/field/attachable.rb +51 -0
- data/lib/restme/scope/field/rules.rb +124 -0
- data/lib/restme/scope/filter/rules.rb +102 -0
- data/lib/restme/scope/filter/types/bigger_than_filterable.rb +38 -0
- data/lib/restme/scope/filter/types/bigger_than_or_equal_to_filterable.rb +38 -0
- data/lib/restme/scope/filter/types/equal_filterable.rb +38 -0
- data/lib/restme/scope/filter/types/in_filterable.rb +46 -0
- data/lib/restme/scope/filter/types/less_than_filterable.rb +38 -0
- data/lib/restme/scope/filter/types/less_than_or_equal_to_filterable.rb +38 -0
- data/lib/restme/scope/filter/types/like_filterable.rb +40 -0
- data/lib/restme/scope/paginate/rules.rb +53 -0
- data/lib/restme/scope/rules.rb +100 -0
- data/lib/restme/scope/sort/rules.rb +67 -0
- data/lib/restme/shared/controller_params.rb +24 -0
- data/lib/restme/shared/current_model.rb +27 -0
- data/lib/restme/shared/user_role.rb +12 -0
- data/lib/restme/update/rules.rb +116 -0
- data/lib/restme/version.rb +5 -0
- data/lib/restme.rb +9 -0
- data/sig/restme.rbs +4 -0
- metadata +75 -0
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Restme
|
4
|
+
module Scope
|
5
|
+
module Paginate
|
6
|
+
# Defines pagination rules
|
7
|
+
module Rules
|
8
|
+
DEFAULT_PER_PAGE = ENV.fetch("PAGINATION_DEFAULT_PER_PAGE", 12)
|
9
|
+
DEFAULT_PAGE = ENV.fetch("PAGINATION_DEFAULT_PAGE", 1)
|
10
|
+
MAX_PER_PAGE = ENV.fetch("PAGINATION_MAX_PER_PAGE", 100)
|
11
|
+
|
12
|
+
def paginable_scope(user_scope)
|
13
|
+
per_page_error
|
14
|
+
|
15
|
+
user_scope.limit(per_page).offset(paginate_offset)
|
16
|
+
end
|
17
|
+
|
18
|
+
def page_no
|
19
|
+
params[:page]&.to_i || DEFAULT_PAGE
|
20
|
+
end
|
21
|
+
|
22
|
+
def pages(user_scope)
|
23
|
+
(total_items(user_scope) / per_page.to_f).ceil
|
24
|
+
end
|
25
|
+
|
26
|
+
def total_items(user_scope)
|
27
|
+
@total_items ||= user_scope.size
|
28
|
+
end
|
29
|
+
|
30
|
+
def per_page
|
31
|
+
params[:per_page]&.to_i || DEFAULT_PER_PAGE
|
32
|
+
end
|
33
|
+
|
34
|
+
def paginate_offset
|
35
|
+
(page_no - 1) * per_page
|
36
|
+
end
|
37
|
+
|
38
|
+
def per_page_error
|
39
|
+
return if per_page <= MAX_PER_PAGE
|
40
|
+
|
41
|
+
restme_scope_errors(
|
42
|
+
{
|
43
|
+
message: "Invalid per page value",
|
44
|
+
body: { per_page_max_value: MAX_PER_PAGE }
|
45
|
+
}
|
46
|
+
)
|
47
|
+
|
48
|
+
restme_scope_status(:bad_request)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../shared/user_role"
|
4
|
+
require_relative "../shared/current_model"
|
5
|
+
require_relative "../shared/controller_params"
|
6
|
+
require_relative "filter/rules"
|
7
|
+
require_relative "sort/rules"
|
8
|
+
require_relative "paginate/rules"
|
9
|
+
require_relative "field/rules"
|
10
|
+
|
11
|
+
module Restme
|
12
|
+
module Scope
|
13
|
+
# Defines the user scope when viewing records.
|
14
|
+
# It can apply pagination, field selection, sorting, and filtering.
|
15
|
+
# Returns records based on the user's contextual rules.
|
16
|
+
module Rules
|
17
|
+
include ::Restme::Scope::Field::Rules
|
18
|
+
include ::Restme::Scope::Paginate::Rules
|
19
|
+
include ::Restme::Scope::Sort::Rules
|
20
|
+
include ::Restme::Scope::Filter::Rules
|
21
|
+
include ::Restme::Shared::ControllerParams
|
22
|
+
include ::Restme::Shared::CurrentModel
|
23
|
+
include ::Restme::Shared::UserRole
|
24
|
+
|
25
|
+
attr_reader :filtered_scope, :sorted_scope, :paginated_scope, :fieldated_scope
|
26
|
+
attr_accessor :restme_scope_errors, :restme_scope_status
|
27
|
+
|
28
|
+
def pagination_response
|
29
|
+
@pagination_response ||= restme_response
|
30
|
+
end
|
31
|
+
|
32
|
+
def model_scope_object
|
33
|
+
@model_scope_object ||= model_scope.first
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def restme_response
|
39
|
+
model_scope
|
40
|
+
|
41
|
+
restme_scope_errors.presence || {
|
42
|
+
objects: model_scope,
|
43
|
+
pagination: pagination
|
44
|
+
}
|
45
|
+
end
|
46
|
+
|
47
|
+
def model_scope
|
48
|
+
@model_scope ||= without_item_in_scope? ? user_scope : custom_scope
|
49
|
+
end
|
50
|
+
|
51
|
+
def pagination
|
52
|
+
{
|
53
|
+
page: page_no,
|
54
|
+
pages: pages(model_scope),
|
55
|
+
total_items: total_items(model_scope)
|
56
|
+
}
|
57
|
+
end
|
58
|
+
|
59
|
+
def restme_scope_errors(error = nil)
|
60
|
+
@restme_scope_errors ||= error
|
61
|
+
end
|
62
|
+
|
63
|
+
def restme_scope_status(status = :ok)
|
64
|
+
@restme_scope_status ||= status
|
65
|
+
end
|
66
|
+
|
67
|
+
def without_item_in_scope?
|
68
|
+
!user_scope.exists?
|
69
|
+
end
|
70
|
+
|
71
|
+
def custom_scope
|
72
|
+
@filtered_scope = filterable_scope(user_scope)
|
73
|
+
@sorted_scope = sortable_scope(filtered_scope)
|
74
|
+
@paginated_scope = paginable_scope(sorted_scope)
|
75
|
+
@fieldated_scope = fieldable_scope(paginated_scope)
|
76
|
+
end
|
77
|
+
|
78
|
+
def user_scope
|
79
|
+
@user_scope ||= super_admin_scope || scope_rules_class.try(method_scope) || none_scope
|
80
|
+
end
|
81
|
+
|
82
|
+
def super_admin_scope
|
83
|
+
klass.all if restme_current_user&.super_admin? || restme_current_user.blank?
|
84
|
+
end
|
85
|
+
|
86
|
+
def none_scope
|
87
|
+
klass.none
|
88
|
+
end
|
89
|
+
|
90
|
+
def method_scope
|
91
|
+
"#{user_role}_scope"
|
92
|
+
end
|
93
|
+
|
94
|
+
def scope_rules_class
|
95
|
+
"#{controller_class.to_s.split("::").last}::Scope::Rules"
|
96
|
+
.constantize.new(klass, restme_current_user, params)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Restme
|
4
|
+
module Scope
|
5
|
+
module Sort
|
6
|
+
# Defines the rules used to sort the records.
|
7
|
+
module Rules
|
8
|
+
ID = :id
|
9
|
+
SORT_KEY = "sort"
|
10
|
+
SORTABLE_TYPES = %w[asc desc].freeze
|
11
|
+
|
12
|
+
def sortable_scope(user_scope)
|
13
|
+
return user_scope unless sortable_scope?
|
14
|
+
return user_scope if unknown_sortable_fields_errors
|
15
|
+
|
16
|
+
user_scope.order(serialize_sort_params)
|
17
|
+
end
|
18
|
+
|
19
|
+
def sortable_scope?
|
20
|
+
request.get? && controller_params_sortable_fields.present?
|
21
|
+
end
|
22
|
+
|
23
|
+
def serialize_sort_params
|
24
|
+
@serialize_sort_params ||= controller_params_sortable_fields.map do |key, value|
|
25
|
+
key = key.to_s.gsub("_#{SORT_KEY}", "")
|
26
|
+
|
27
|
+
value = "asc" unless SORTABLE_TYPES.include?(value&.downcase)
|
28
|
+
|
29
|
+
{ "#{key}": value&.downcase }
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def controller_params_sortable_fields
|
34
|
+
@controller_params_sortable_fields ||= controller_query_params.select do |item|
|
35
|
+
item.to_s.end_with?(SORT_KEY)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def unknown_sortable_fields
|
40
|
+
@unknown_sortable_fields ||=
|
41
|
+
serialize_sort_params.map { |sort_param| sort_param.first.first } - sortable_fields
|
42
|
+
end
|
43
|
+
|
44
|
+
def unknown_sortable_fields_errors
|
45
|
+
return unless unknown_sortable_fields.present?
|
46
|
+
|
47
|
+
restme_scope_errors(
|
48
|
+
{
|
49
|
+
message: "Unknown Sort",
|
50
|
+
body: unknown_sortable_fields
|
51
|
+
}
|
52
|
+
)
|
53
|
+
|
54
|
+
restme_scope_status(:bad_request)
|
55
|
+
|
56
|
+
true
|
57
|
+
end
|
58
|
+
|
59
|
+
def sortable_fields
|
60
|
+
@sortable_fields ||= Array.new(klass::SORTABLE_FIELDS).push(ID)
|
61
|
+
rescue StandardError
|
62
|
+
[ID]
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Restme
|
4
|
+
module Shared
|
5
|
+
# Returns both the query string parameters and the request body parameters received by the controller.
|
6
|
+
module ControllerParams
|
7
|
+
def controller_params
|
8
|
+
params_filtered.permit!.to_h.values.first.deep_symbolize_keys
|
9
|
+
rescue StandardError
|
10
|
+
params_filtered.permit!.to_h.deep_symbolize_keys
|
11
|
+
end
|
12
|
+
|
13
|
+
def controller_query_params
|
14
|
+
@controller_query_params ||= request.query_parameters.deep_symbolize_keys
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def params_filtered
|
20
|
+
params.except(:controller, :action)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Restme
|
4
|
+
module Shared
|
5
|
+
# Returns the model associated with the controller.
|
6
|
+
# It tries to determine the model dynamically or uses the MODEL_NAME constant if defined in the controller.
|
7
|
+
module CurrentModel
|
8
|
+
def klass
|
9
|
+
return defined_model_name if defined_model_name
|
10
|
+
|
11
|
+
controller_class.to_s.split("::").last.remove("sController").constantize
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def defined_model_name
|
17
|
+
return unless controller_class.const_defined?(:MODEL_NAME)
|
18
|
+
|
19
|
+
controller_class::MODEL_NAME.constantize
|
20
|
+
end
|
21
|
+
|
22
|
+
def controller_class
|
23
|
+
self.class
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../shared/user_role"
|
4
|
+
require_relative "../shared/current_model"
|
5
|
+
require_relative "../shared/controller_params"
|
6
|
+
|
7
|
+
module Restme
|
8
|
+
module Update
|
9
|
+
# Defines update restrictions rules for a record.
|
10
|
+
module Rules
|
11
|
+
include ::Restme::Shared::ControllerParams
|
12
|
+
include ::Restme::Shared::CurrentModel
|
13
|
+
include ::Restme::Shared::UserRole
|
14
|
+
|
15
|
+
attr_reader :update_temp_record
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def updateable_record
|
20
|
+
@updateable_record ||= begin
|
21
|
+
@update_temp_record = klass.find_by(id: params[:id])
|
22
|
+
|
23
|
+
set_update_temp_record_current_user
|
24
|
+
|
25
|
+
update_temp_record.assign_attributes(controller_params)
|
26
|
+
|
27
|
+
update_record_errors.presence || update_temp_record
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def set_update_temp_record_current_user
|
32
|
+
return unless update_temp_record.respond_to?(:current_user)
|
33
|
+
return unless restme_current_user
|
34
|
+
|
35
|
+
update_temp_record.current_user = restme_current_user
|
36
|
+
end
|
37
|
+
|
38
|
+
def restme_update_status
|
39
|
+
return :unprocessable_entity if update_record_errors
|
40
|
+
|
41
|
+
:ok
|
42
|
+
end
|
43
|
+
|
44
|
+
def update_record_errors
|
45
|
+
return unless updateable_current_action
|
46
|
+
|
47
|
+
return updateable_not_found_error if update_temp_record.blank?
|
48
|
+
return updateable_unscoped_error_response unless updateable_scope?
|
49
|
+
|
50
|
+
update_object! unless @update_result
|
51
|
+
|
52
|
+
updateable_record_errors_messages
|
53
|
+
end
|
54
|
+
|
55
|
+
def update_object!
|
56
|
+
@update_result ||= ActiveRecord::Base.transaction do
|
57
|
+
update_temp_record.save!
|
58
|
+
end
|
59
|
+
rescue StandardError
|
60
|
+
nil
|
61
|
+
end
|
62
|
+
|
63
|
+
def updateable_not_found_error
|
64
|
+
{
|
65
|
+
message: "Not found object to id: #{params[:id]}",
|
66
|
+
body: controller_params
|
67
|
+
}
|
68
|
+
end
|
69
|
+
|
70
|
+
def updateable_unscoped_error_response
|
71
|
+
{ message: "Unscoped", body: controller_params }
|
72
|
+
end
|
73
|
+
|
74
|
+
def updateable_scope?
|
75
|
+
return true unless restme_current_user
|
76
|
+
|
77
|
+
method_scope = "#{updateable_current_action}_#{user_role}_scope?"
|
78
|
+
|
79
|
+
updateable_super_admin_scope? || update_rules_class.try(method_scope) || false
|
80
|
+
end
|
81
|
+
|
82
|
+
def updateable_super_admin_scope?
|
83
|
+
restme_current_user&.super_admin?
|
84
|
+
end
|
85
|
+
|
86
|
+
def updateable_record_errors_messages
|
87
|
+
return if update_temp_record.errors.blank?
|
88
|
+
|
89
|
+
"#{current_action}_error_scope?".constantize
|
90
|
+
rescue StandardError
|
91
|
+
updateable_default_error_response
|
92
|
+
end
|
93
|
+
|
94
|
+
def updateable_current_action
|
95
|
+
return true unless restme_current_user
|
96
|
+
|
97
|
+
current_action.presence_in update_rules_class.class::UPDATABLE_ACTIONS_RULES
|
98
|
+
rescue StandardError
|
99
|
+
nil
|
100
|
+
end
|
101
|
+
|
102
|
+
def updateable_default_error_response
|
103
|
+
{ message: "Error #{update_temp_record.errors.full_messages.to_sentence}" }
|
104
|
+
end
|
105
|
+
|
106
|
+
def current_action
|
107
|
+
action_name.to_sym
|
108
|
+
end
|
109
|
+
|
110
|
+
def update_rules_class
|
111
|
+
@update_rules_class ||= "#{controller_class.to_s.split("::").last}::Update::Rules"
|
112
|
+
.constantize.new(update_temp_record, restme_current_user, controller_params)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
data/lib/restme.rb
ADDED
data/sig/restme.rbs
ADDED
metadata
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: restme
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.37
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- everson-ever
|
8
|
+
bindir: exe
|
9
|
+
cert_chain: []
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
11
|
+
dependencies: []
|
12
|
+
description: Add filter/pagination/fields select/sort support to your API controllers
|
13
|
+
email:
|
14
|
+
- eversonsilva9799@gmail.com
|
15
|
+
executables: []
|
16
|
+
extensions: []
|
17
|
+
extra_rdoc_files: []
|
18
|
+
files:
|
19
|
+
- ".rspec"
|
20
|
+
- ".rubocop.yml"
|
21
|
+
- CHANGELOG.md
|
22
|
+
- CODE_OF_CONDUCT.md
|
23
|
+
- Dockerfile
|
24
|
+
- LICENSE.txt
|
25
|
+
- README.md
|
26
|
+
- Rakefile
|
27
|
+
- docker-compose.yml
|
28
|
+
- lib/restme.rb
|
29
|
+
- lib/restme/authorize/rules.rb
|
30
|
+
- lib/restme/create/rules.rb
|
31
|
+
- lib/restme/restme.rb
|
32
|
+
- lib/restme/scope/field/attachable.rb
|
33
|
+
- lib/restme/scope/field/rules.rb
|
34
|
+
- lib/restme/scope/filter/rules.rb
|
35
|
+
- lib/restme/scope/filter/types/bigger_than_filterable.rb
|
36
|
+
- lib/restme/scope/filter/types/bigger_than_or_equal_to_filterable.rb
|
37
|
+
- lib/restme/scope/filter/types/equal_filterable.rb
|
38
|
+
- lib/restme/scope/filter/types/in_filterable.rb
|
39
|
+
- lib/restme/scope/filter/types/less_than_filterable.rb
|
40
|
+
- lib/restme/scope/filter/types/less_than_or_equal_to_filterable.rb
|
41
|
+
- lib/restme/scope/filter/types/like_filterable.rb
|
42
|
+
- lib/restme/scope/paginate/rules.rb
|
43
|
+
- lib/restme/scope/rules.rb
|
44
|
+
- lib/restme/scope/sort/rules.rb
|
45
|
+
- lib/restme/shared/controller_params.rb
|
46
|
+
- lib/restme/shared/current_model.rb
|
47
|
+
- lib/restme/shared/user_role.rb
|
48
|
+
- lib/restme/update/rules.rb
|
49
|
+
- lib/restme/version.rb
|
50
|
+
- sig/restme.rbs
|
51
|
+
homepage: https://github.com/everson-ever/restme
|
52
|
+
licenses:
|
53
|
+
- MIT
|
54
|
+
metadata:
|
55
|
+
homepage_uri: https://github.com/everson-ever/restme
|
56
|
+
source_code_uri: https://github.com/everson-ever/restme
|
57
|
+
changelog_uri: https://github.com/everson-ever/restme/blob/main/CHANGELOG.md
|
58
|
+
rdoc_options: []
|
59
|
+
require_paths:
|
60
|
+
- lib
|
61
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
62
|
+
requirements:
|
63
|
+
- - ">="
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: 3.0.0
|
66
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
67
|
+
requirements:
|
68
|
+
- - ">="
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
version: '0'
|
71
|
+
requirements: []
|
72
|
+
rubygems_version: 3.6.7
|
73
|
+
specification_version: 4
|
74
|
+
summary: Rest API support
|
75
|
+
test_files: []
|