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.
@@ -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,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Restme
4
+ module Shared
5
+ # Returns the roles associated with the user, if any exist.
6
+ module UserRole
7
+ def user_role
8
+ restme_current_user&.role
9
+ end
10
+ end
11
+ end
12
+ 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
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Restme
4
+ VERSION = "0.0.37"
5
+ end
data/lib/restme.rb ADDED
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "restme/version"
4
+ require_relative "restme/restme"
5
+
6
+ module Restme
7
+ class Error < StandardError; end
8
+ # Your code goes here...
9
+ end
data/sig/restme.rbs ADDED
@@ -0,0 +1,4 @@
1
+ module Restme
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
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: []