From abbbf4d6bfb0b6c774092c5fe784107d543bf5ba Mon Sep 17 00:00:00 2001 From: Chris Beer Date: Tue, 24 Nov 2020 06:54:14 -0800 Subject: [PATCH] Extract Types::MarcRecordQuery --- app/controllers/graphql_controller.rb | 3 +- app/graphql/types/base_object.rb | 4 ++ app/graphql/types/marc_records_query.rb | 67 +++++++++++++++++++++++++ app/graphql/types/organization_type.rb | 53 ++----------------- app/graphql/types/query_type.rb | 23 ++++++++- 5 files changed, 98 insertions(+), 52 deletions(-) create mode 100644 app/graphql/types/marc_records_query.rb diff --git a/app/controllers/graphql_controller.rb b/app/controllers/graphql_controller.rb index 7ccef2fa..038ca9f8 100644 --- a/app/controllers/graphql_controller.rb +++ b/app/controllers/graphql_controller.rb @@ -16,8 +16,7 @@ def execute query = params[:query] operation_name = params[:operationName] context = { - # Query context goes here, for example: - # current_user: current_user, + current_ability: current_ability } result = AggregatorSchema.execute(query, variables: variables, context: context, operation_name: operation_name) render json: result diff --git a/app/graphql/types/base_object.rb b/app/graphql/types/base_object.rb index 462b1739..f6df5303 100644 --- a/app/graphql/types/base_object.rb +++ b/app/graphql/types/base_object.rb @@ -4,5 +4,9 @@ module Types # :nodoc: class BaseObject < GraphQL::Schema::Object field_class Types::BaseField + + def current_ability + @current_ability ||= context[:current_ability] || Ability.new + end end end diff --git a/app/graphql/types/marc_records_query.rb b/app/graphql/types/marc_records_query.rb new file mode 100644 index 00000000..aa164bd8 --- /dev/null +++ b/app/graphql/types/marc_records_query.rb @@ -0,0 +1,67 @@ +module Types + module MarcRecordsQuery + def with_limit(limit, what, **args, &block) + return send(what, **args, &block) unless limit + + i = 0 + + send(what, **args).each do |record| + yield record + i += 1 + break if i >= limit + end + end + + def each_record(organization = nil, &block) + return to_enum(:each_record, organization) unless block + + l = Array(organization) if organization + l ||= Organization.accessible_by(current_ability) + + l.each do |org| + org.default_stream.uploads.find_each do |upload| + upload.each_marc_record_metadata(&block) + end + end + end + + def filter_records(filter: nil, organization: nil, &block) + return to_enum(:filter_records, filter: filter, organization: organization) unless block + + return each_record(organization, &block) unless filter + + each_record(organization) do |record| + yield record if filter.all? { |f| evaluate_marcspec_filter(record.marc, f) } + end + end + + def evaluate_marcspec_filter(record, filter) + if filter.include?('$') + field, subfield = filter.split('$', 2) + + matching_fields(field).any? do |f| + f[subfield] + end + elsif filter.starts_with? 'LDR' + record.leader + elsif filter.match?(/^\d{3}[a-z]$/) + field = filter[0..2] + subfield = filter[3] + evaluate_marcspec_filter(record, "#{field}$#{subfield}") + else + matching_fields(filter).any? + end + end + + def matching_fields(record, field) + return record.fields(filter) if field.match?(/[a-z0-9A-Z]{3}/) + return to_enum(:matching_fields, record, field) unless block_given? + + field_as_regex = Regexp.new(field) + + record.fields.each do |f| + yield f if f.tag.match?(field_as_regex) + end + end + end +end diff --git a/app/graphql/types/organization_type.rb b/app/graphql/types/organization_type.rb index 4201e18f..e8be1ea2 100644 --- a/app/graphql/types/organization_type.rb +++ b/app/graphql/types/organization_type.rb @@ -3,7 +3,9 @@ module Types # :nodoc: class OrganizationType < Types::BaseObject - field :id, ID, null: false + include Types::MarcRecordsQuery + + field :slug, ID, null: true field :name, String, null: true field :created_at, GraphQL::Types::ISO8601DateTime, null: false field :updated_at, GraphQL::Types::ISO8601DateTime, null: false @@ -13,7 +15,6 @@ class OrganizationType < Types::BaseObject # field :normalization_steps, Types::JsonType, null: true field :public, Boolean, null: true field :name, String, null: true - field :slug, String, null: true field :records, [MarcRecordType], null: true do description 'Return all MARC records' argument :filter, [String], required: false @@ -23,53 +24,7 @@ class OrganizationType < Types::BaseObject def records(limit: 25, **args, &block) return to_enum(:records, **args, limit: limit) unless block - with_limit(limit, **args, &block) - end - - def with_limit(limit, **args, &block) - return filter_records(**args, &block) unless limit - - i = 0 - - filter_records(**args).each do |record| - yield record - i += 1 - break if i >= limit - end - end - - def each_record(&block) - return to_enum(:each_record) unless block - - object.default_stream.uploads.find_each do |upload| - upload.each_marc_record_metadata(&block) - end - end - - def filter_records(filter: nil, &block) - return to_enum(:filter_records, filter: filter) unless block - - return each_record(&block) unless filter - - each_record do |record| - yield record if filter.all? { |f| evaluate_marcspec_filter(record.marc, f) } - end - end - - def evaluate_marcspec_filter(record, filter) - if filter.include?('$') - field, subfield = filter.split('$', 2) - - record.fields(field).any? do |f| - f[subfield] - end - elsif filter.match?(/^\d{3}[a-z]$/) - field = filter[0..2] - subfield = filter[3] - evaluate_marcspec_filter(record, "#{field}$#{subfield}") - else - record.marc[filter] - end + with_limit(limit, :filter_records, organization: object, **args, &block) end end end diff --git a/app/graphql/types/query_type.rb b/app/graphql/types/query_type.rb index 3bbc88ca..e400edd1 100644 --- a/app/graphql/types/query_type.rb +++ b/app/graphql/types/query_type.rb @@ -3,16 +3,37 @@ module Types # :nodoc: class QueryType < Types::BaseObject + include Types::MarcRecordsQuery # Add root-level fields here. # They will be entry points for queries on your schema. + field :organizations, [OrganizationType], null: false do + description 'List all organizations' + end + field :organization, OrganizationType, null: false do description 'Find an organization by ID' argument :id, ID, required: true end + field :records, [MarcRecordType], null: false do + description 'List all marc records' + argument :filter, [String], required: false + argument :limit, Integer, required: false + end + def organization(id:) - Organization.friendly.find(id) + Organization.accessible_by(current_ability).friendly.find(id) + end + + def organizations + Organization.accessible_by(current_ability) + end + + def records(limit: 25, **args, &block) + return to_enum(:records, **args, limit: limit) unless block + + with_limit(limit, :filter_records, **args, &block) end end end