Add graphql object types
This commit is contained in:
parent
9bb52dfb8c
commit
52e84f2ffe
8 changed files with 240 additions and 0 deletions
36
app/graphql/extensions/attachment.rb
Normal file
36
app/graphql/extensions/attachment.rb
Normal file
|
@ -0,0 +1,36 @@
|
|||
# references:
|
||||
# https://evilmartians.com/chronicles/active-storage-meets-graphql-pt-2-exposing-attachment-urls
|
||||
|
||||
module Extensions
|
||||
class Attachment < GraphQL::Schema::FieldExtension
|
||||
attr_reader :attachment_assoc
|
||||
|
||||
def apply
|
||||
# Here we try to define the attachment name:
|
||||
# - it could be set explicitly via extension options
|
||||
# - or we imply that is the same as the field name w/o "_url"
|
||||
# suffix (e.g., "avatar_url" => "avatar")
|
||||
attachment = options&.[](:attachment) || field.original_name.to_s.sub(/_url$/, "")
|
||||
|
||||
# that's the name of the Active Record association
|
||||
@attachment_assoc = "#{attachment}_attachment"
|
||||
end
|
||||
|
||||
# This method resolves (as it states) the field itself
|
||||
# (it's the same as defining a method within a type)
|
||||
def resolve(object:, **_rest)
|
||||
Loaders::Association.for(
|
||||
object.object.class,
|
||||
attachment_assoc => :blob
|
||||
).load(object.object)
|
||||
end
|
||||
|
||||
# This method is called if the result of the `resolve`
|
||||
# is a lazy value (e.g., a Promise – like in our case)
|
||||
def after_resolve(value:, **_rest)
|
||||
return if value.nil?
|
||||
|
||||
Rails.application.routes.url_helpers.url_for(value)
|
||||
end
|
||||
end
|
||||
end
|
66
app/graphql/loaders/association.rb
Normal file
66
app/graphql/loaders/association.rb
Normal file
|
@ -0,0 +1,66 @@
|
|||
# references:
|
||||
# https://github.com/Shopify/graphql-batch/blob/master/examples/association_loader.rb
|
||||
# https://gist.github.com/palkan/03eb5306a1a3e8addbe8df97a298a466
|
||||
# https://evilmartians.com/chronicles/active-storage-meets-graphql-pt-2-exposing-attachment-urls
|
||||
|
||||
module Loaders
|
||||
class Association < GraphQL::Batch::Loader
|
||||
def self.validate(model, association_name)
|
||||
new(model, association_name)
|
||||
nil
|
||||
end
|
||||
|
||||
def initialize(model, association_schema)
|
||||
@model = model
|
||||
@association_schema = association_schema
|
||||
@association_name = extract_association_id(association_schema)
|
||||
validate
|
||||
end
|
||||
|
||||
def load(record)
|
||||
raise TypeError, "#{@model} loader can't load association for #{record.class}" unless record.is_a?(@model)
|
||||
return Promise.resolve(read_association(record)) if association_loaded?(record)
|
||||
super
|
||||
end
|
||||
|
||||
# We want to load the associations on all records, even if they have the same id
|
||||
def cache_key(record)
|
||||
record.object_id
|
||||
end
|
||||
|
||||
def perform(records)
|
||||
preload_association(records.uniq)
|
||||
records.each { |record| fulfill(record, read_association(record)) }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def validate
|
||||
unless @model.reflect_on_association(@association_name)
|
||||
raise ArgumentError, "No association #{@association_name} on #{@model}"
|
||||
end
|
||||
end
|
||||
|
||||
def preload_association(records)
|
||||
::ActiveRecord::Associations::Preloader.new.preload(records, @association_schema)
|
||||
end
|
||||
|
||||
def read_association(record)
|
||||
record.public_send(@association_name)
|
||||
end
|
||||
|
||||
def association_loaded?(record)
|
||||
record.association(@association_name).loaded?
|
||||
end
|
||||
|
||||
def extract_association_id(id_or_hash)
|
||||
return id_or_hash unless id_or_hash.is_a?(Hash)
|
||||
|
||||
if id_or_hash.keys.size != 1
|
||||
raise ArgumentError, "You can only preload exactly one association! You passed: #{id_or_hash}"
|
||||
end
|
||||
|
||||
id_or_hash.keys.first
|
||||
end
|
||||
end
|
||||
end
|
30
app/graphql/loaders/record.rb
Normal file
30
app/graphql/loaders/record.rb
Normal file
|
@ -0,0 +1,30 @@
|
|||
# references:
|
||||
# https://github.com/Shopify/graphql-batch/blob/master/examples/record_loader.rb
|
||||
|
||||
module Loaders
|
||||
class Record < GraphQL::Batch::Loader
|
||||
def initialize(model, column: model.primary_key, where: nil)
|
||||
@model = model
|
||||
@column = column.to_s
|
||||
@column_type = model.type_for_attribute(@column)
|
||||
@where = where
|
||||
end
|
||||
|
||||
def load(key)
|
||||
super(@column_type.cast(key))
|
||||
end
|
||||
|
||||
def perform(keys)
|
||||
query(keys).each { |record| fulfill(record.public_send(@column), record) }
|
||||
keys.each { |key| fulfill(key, nil) unless fulfilled?(key) }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def query(keys)
|
||||
scope = @model
|
||||
scope = scope.where(@where) if @where
|
||||
scope.where(@column => keys)
|
||||
end
|
||||
end
|
||||
end
|
50
app/graphql/types/demarche_type.rb
Normal file
50
app/graphql/types/demarche_type.rb
Normal file
|
@ -0,0 +1,50 @@
|
|||
module Types
|
||||
class DemarcheType < Types::BaseObject
|
||||
class DemarcheState < Types::BaseEnum
|
||||
Procedure.aasm.states.reject { |state| state.name == :hidden }.each do |state|
|
||||
value(state.name.to_s, state.display_name, value: state.name)
|
||||
end
|
||||
end
|
||||
|
||||
description "Une demarche"
|
||||
|
||||
global_id_field :id
|
||||
field :number, ID, "Le numero de la démarche.", null: false, method: :id
|
||||
field :title, String, null: false, method: :libelle
|
||||
field :description, String, "Déscription de la démarche.", null: false
|
||||
field :state, DemarcheState, null: false
|
||||
|
||||
field :created_at, GraphQL::Types::ISO8601DateTime, null: false
|
||||
field :updated_at, GraphQL::Types::ISO8601DateTime, null: false
|
||||
field :archived_at, GraphQL::Types::ISO8601DateTime, null: true
|
||||
|
||||
field :instructeurs, [Types::ProfileType], null: false
|
||||
|
||||
field :dossiers, Types::DossierType.connection_type, "Liste de tous les dossiers d'une démarche.", null: false do
|
||||
argument :ids, [ID], required: false, description: "Filtrer les dossiers par ID."
|
||||
argument :since, GraphQL::Types::ISO8601DateTime, required: false, description: "Dossiers crées depuis la date."
|
||||
end
|
||||
|
||||
def state
|
||||
object.aasm.current_state
|
||||
end
|
||||
|
||||
def instructeurs
|
||||
Loaders::Association.for(Procedure, :instructeurs).load(object)
|
||||
end
|
||||
|
||||
def dossiers(ids: nil, since: nil)
|
||||
dossiers = object.dossiers.for_api_v2
|
||||
|
||||
if ids.present?
|
||||
dossiers = dossiers.where(id: ids)
|
||||
end
|
||||
|
||||
if since.present?
|
||||
dossiers = dossiers.since(since)
|
||||
end
|
||||
|
||||
dossiers
|
||||
end
|
||||
end
|
||||
end
|
42
app/graphql/types/dossier_type.rb
Normal file
42
app/graphql/types/dossier_type.rb
Normal file
|
@ -0,0 +1,42 @@
|
|||
module Types
|
||||
class DossierType < Types::BaseObject
|
||||
class DossierState < Types::BaseEnum
|
||||
Dossier.aasm.states.reject { |state| state.name == :brouillon }.each do |state|
|
||||
value(state.name.to_s, state.display_name, value: state.name.to_s)
|
||||
end
|
||||
end
|
||||
|
||||
description "Un dossier"
|
||||
|
||||
global_id_field :id
|
||||
field :number, ID, "Le numero du dossier.", null: false, method: :id
|
||||
field :state, DossierState, "L'état du dossier.", null: false
|
||||
field :updated_at, GraphQL::Types::ISO8601DateTime, "Date de dernière mise à jour.", null: false
|
||||
|
||||
field :date_passage_en_construction, GraphQL::Types::ISO8601DateTime, "Date de dépôt.", null: false, method: :en_construction_at
|
||||
field :date_passage_en_instruction, GraphQL::Types::ISO8601DateTime, "Date de passage en instruction.", null: true, method: :en_instruction_at
|
||||
field :date_traitement, GraphQL::Types::ISO8601DateTime, "Date de traitement.", null: true, method: :processed_at
|
||||
|
||||
field :archived, Boolean, null: false
|
||||
|
||||
field :motivation, String, null: true
|
||||
field :motivation_attachment_url, Types::URL, null: true, extensions: [
|
||||
{ Extensions::Attachment => { attachment: :justificatif_motivation } }
|
||||
]
|
||||
|
||||
field :usager, Types::ProfileType, null: false
|
||||
field :instructeurs, [Types::ProfileType], null: false
|
||||
|
||||
def state
|
||||
object.state
|
||||
end
|
||||
|
||||
def usager
|
||||
Loaders::Record.for(User).load(object.user_id)
|
||||
end
|
||||
|
||||
def instructeurs
|
||||
Loaders::Association.for(object.class, :followers_instructeurs).load(object)
|
||||
end
|
||||
end
|
||||
end
|
6
app/graphql/types/profile_type.rb
Normal file
6
app/graphql/types/profile_type.rb
Normal file
|
@ -0,0 +1,6 @@
|
|||
module Types
|
||||
class ProfileType < Types::BaseObject
|
||||
global_id_field :id
|
||||
field :email, String, null: false
|
||||
end
|
||||
end
|
|
@ -17,3 +17,9 @@ fr:
|
|||
refuse: "Refusé"
|
||||
sans_suite: "Sans suite"
|
||||
autorisation_donnees: Acceptation des CGU
|
||||
state/brouillon: Brouillon
|
||||
state/en_construction: En construction
|
||||
state/en_instruction: En instruction
|
||||
state/accepte: Accepté
|
||||
state/refuse: Refusé
|
||||
state/sans_suite: Sans suite
|
||||
|
|
|
@ -10,3 +10,7 @@ fr:
|
|||
organisation: Organisme
|
||||
duree_conservation_dossiers_dans_ds: Durée de conservation des dossiers sur demarches-simplifiees.fr
|
||||
duree_conservation_dossiers_hors_ds: Durée de conservation des dossiers hors demarches-simplifiees.fr
|
||||
aasm_state/brouillon: Brouillon
|
||||
aasm_state/publiee: Publiée
|
||||
aasm_state/archivee: Archivée
|
||||
aasm_state/hidden: Suprimée
|
||||
|
|
Loading…
Reference in a new issue