Created
September 4, 2025 13:08
-
-
Save mjerem34/13eae595e7c3b13b12021ef2c8f8f081 to your computer and use it in GitHub Desktop.
JSON::API Spec Helpers
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # app/controllers/api/v3/concerns/errors.rb | |
| # frozen_string_literal: true | |
| module Api | |
| module V3 | |
| module Concerns | |
| module Errors | |
| extend ActiveSupport::Concern | |
| included do | |
| rescue_from ActiveRecord::RecordNotFound do |error| | |
| render_json_errors(error.exception, 404) | |
| end | |
| rescue_from ActionController::ParameterMissing do |error| | |
| render_json_errors(error.exception, 400) | |
| end | |
| end | |
| end | |
| end | |
| end | |
| end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # app/controllers/api/v3/concerns/filters.rb | |
| # frozen_string_literal: true | |
| module Api | |
| module V3 | |
| module Concerns | |
| module Filters | |
| extend ActiveSupport::Concern | |
| def filter(object) | |
| return object unless params[:filter] | |
| return object if action_name != 'index' | |
| model_name = object.model.table_name | |
| object.where(filters(model_name)) | |
| end | |
| private | |
| def filters(model_name) | |
| filter_params.to_h.each do |key, value| | |
| operator = value.split(':')[0] | |
| build_sql_query(model_name, key, value, operator) | |
| build_sql_values(value, operator) | |
| end | |
| return {} if sql_query.blank? | |
| @filters ||= [sql_query.join(' AND '), *sql_values] | |
| end | |
| def build_sql_values(value, operator) | |
| return if expected_value(value) == 'null' | |
| return handle_sql_values_for_between_operator(value) if operator['between'] | |
| sql_values << expected_value(value).split(',') | |
| end | |
| # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity | |
| def build_sql_query(model_name, key, value, operator) | |
| sql_query << equal_query(model_name, key, value) if operator['equals'] | |
| sql_query << not_equal_query(model_name, key, value) if operator['notEquals'] | |
| sql_query << "#{model_name}.#{key} > (?)" if operator['greaterThan'] | |
| sql_query << "#{model_name}.#{key} >= (?)" if operator['greaterOrEqual'] | |
| sql_query << "#{model_name}.#{key} < (?)" if operator['lessThan'] | |
| sql_query << "#{model_name}.#{key} <= (?)" if operator['lessOrEqual'] | |
| sql_query << "#{model_name}.#{key} BETWEEN (?) AND (?)" if operator['between'] | |
| sql_query << "#{model_name}.#{key} @> ARRAY[?]::varchar[]" if operator['contains'] | |
| end | |
| # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity | |
| def handle_sql_values_for_between_operator(value) | |
| expected_value(value).split(',').each { |v| sql_values << v } | |
| end | |
| def expected_value(value) | |
| value.split(':')[1].gsub("'", '') | |
| end | |
| def equal_query(model_name, key, value) | |
| operation = expected_value(value).downcase == 'null' ? 'IS NULL' : 'IN (?)' | |
| "#{model_name}.#{key} #{operation}" | |
| end | |
| def not_equal_query(model_name, key, value) | |
| operation = expected_value(value).downcase == 'null' ? 'IS NOT NULL' : 'NOT IN (?)' | |
| "#{model_name}.#{key} #{operation}" | |
| end | |
| def sql_query | |
| @sql_query ||= [] | |
| end | |
| def sql_values | |
| @sql_values ||= [] | |
| end | |
| def filter_params | |
| params.require(:filter).permit(allowed_filter_params) | |
| end | |
| def allowed_filter_params | |
| [] | |
| end | |
| def formated_value(value) | |
| return nil if value == 'nil' | |
| value&.split(',') | |
| end | |
| end | |
| end | |
| end | |
| end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # app/controllers/api/v3/lorems/ipsums_controller.rb | |
| # frozen_string_literal: true | |
| module Api | |
| module V3 | |
| module Lorems | |
| class IpsumsController < Api::V3::Lorems::BaseController | |
| def index | |
| render_serialized_json(lorem.impsums, serializer: ::V3::IpsumsSerializer) | |
| end | |
| private | |
| def allowed_filter_params | |
| %i[end_at start_at] | |
| end | |
| def allowed_order_params | |
| %i[start_at] | |
| end | |
| end | |
| end | |
| end | |
| end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # app/controllers/api/v3/concerns/order.rb | |
| # frozen_string_literal: true | |
| module Api | |
| module V3 | |
| module Concerns | |
| module Order | |
| extend ActiveSupport::Concern | |
| def order(object) | |
| return object unless params[:sort] | |
| return object if allowed_order_params.blank? | |
| return object unless object.try(:length) && object.length > 1 | |
| object.reorder(order_method) | |
| end | |
| private | |
| def order_method | |
| order_params.select do |param| | |
| param.inject(:merge).first.to_sym.in?(allowed_order_params) | |
| end | |
| end | |
| def order_params | |
| params.require(:sort).split(',').map do |param| | |
| param.chr == '-' ? Hash[param.slice(1..-1), :desc] : Hash[param, :asc] | |
| end | |
| end | |
| def allowed_order_params | |
| [] | |
| end | |
| end | |
| end | |
| end | |
| end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # app/controllers/api/v3/concerns/pagination.rb | |
| # frozen_string_literal: true | |
| module Api | |
| module V3 | |
| module Concerns | |
| module Pagination | |
| include Pagy::Backend | |
| extend ActiveSupport::Concern | |
| def paginate(serializer, resources, options = {}) | |
| paginated_resources = page_all? ? resources : paginate_resources(resources) | |
| meta_options = paginated_resources_meta(paginated_resources) | |
| serializer.new(paginated_resources, meta_options.merge(options).with_indifferent_access) | |
| end | |
| private | |
| def paginate_resources(resources) | |
| per = per_param(resources) | |
| if resources.is_a?(Array) | |
| @pagy, @ressources = pagy_array(resources, page: page_param, items: per) | |
| else | |
| @pagy, @ressources = pagy(resources, page: page_param, items: per) | |
| end | |
| @ressources | |
| end | |
| def paginated_resources_meta(resources) | |
| { | |
| meta: { | |
| current_page: @pagy&.page || 1, | |
| next_page: @pagy&.next, | |
| per_page: @pagy&.items || resources&.length, | |
| prev_page: @pagy&.prev, | |
| total_pages: @pagy&.pages || 1, | |
| total_count: @pagy&.count || resources&.length | |
| } | |
| } | |
| end | |
| def page_all? | |
| params[:page] == 'all' | |
| end | |
| def page_param | |
| page_all? || params[:page].blank? ? 1 : params[:page] | |
| end | |
| def per_param(resources) | |
| if page_all? | |
| return resources.length.zero? ? 1 : resources.length | |
| end | |
| params[:per] || 25 | |
| end | |
| end | |
| end | |
| end | |
| end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # app/controllers/api/v3/concerns/response.rb | |
| # frozen_string_literal: true | |
| module Api | |
| module V3 | |
| module Concerns | |
| module Response | |
| include ::Api::V3::Concerns::Filters | |
| include ::Api::V3::Concerns::Order | |
| extend ActiveSupport::Concern | |
| def render_serialized_json(object, status = 200, serializer: nil, render_options: {}) | |
| filtered_object = filter(object) | |
| ordered_filtered_object = order(filtered_object) | |
| json = serialized_objects(ordered_filtered_object, render_options, serializer).to_json | |
| render plain: json, content_type: 'application/json', status: status | |
| end | |
| def render_json_errors(errors, status = 400) | |
| render plain: { errors: Array.wrap(errors) }.to_json, | |
| content_type: 'application/json', status: status | |
| end | |
| private | |
| def serializer_options | |
| includes = params[:include] ? { include: params[:include].split(',') } : {} | |
| fields = if params[:fields] | |
| { fields: params[:fields].permit!.to_h.transform_values { |v| v.split(',') } } | |
| else | |
| {} | |
| end | |
| {}.merge(includes, fields) | |
| end | |
| def serializer_klass(object_name) | |
| _api, version, namespace, *_params = controller_path.split('/').map(&:classify) | |
| serializer_name = [version, namespace, object_name].uniq.join('::') | |
| "#{serializer_name}Serializer".constantize | |
| end | |
| def serializer_pagination(object, serializer, options) | |
| if object.try(:size) | |
| paginate(serializer, object, options) | |
| else | |
| serializer.new(object, options) | |
| end | |
| end | |
| def serialized_objects(object, render_options, serializer = nil) | |
| return {} if object.blank? | |
| object_name = object.respond_to?(:each) ? object.first.class.name : object.class.name | |
| serializer ||= serializer_klass(object_name) | |
| options = serializer_options.merge(render_options) | |
| serializer_pagination(object, serializer, options) | |
| end | |
| end | |
| end | |
| end | |
| end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment