Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 5 additions & 6 deletions lib/graphoid/definitions/filters.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,22 +41,21 @@ def generate(model)
end

Relation.relations_of(model).each do |name, relation|

@GiuseppeXD GiuseppeXD Aug 20, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Before graphoid added every Mongoid association to the generated filter inputs.

input AttachmentFilter {
  record: RecordFilter
}

input RecordFilter {
  attachments_some: AttachmentFilter
  attachments_none: AttachmentFilter
  attachments_every: AttachmentFilter
}

Now, graphql-ruby rejects these during static schema validation:

attachments(where: { record: { id_not: null } }) {
  data { id }
}
InputObject 'AttachmentFilter' doesn't accept argument 'record'

relation_class = relation.class_name.safe_constantize
relation_name = Utils.graphqlize(relation_class.name)
next unless relation.options[:graphoid_nested_filter]

relation_class = relation.class_name.safe_constantize
next unless relation_class

relation_filter = LIST[relation_class]
relation_filter = "Graphoid::Types::#{relation_name}Filter" unless relation_filter

relation_name = Utils.graphqlize(relation_class.name)
relation_filter = LIST[relation_class] || "Graphoid::Types::#{relation_name}Filter"
relation_name = Utils.camelize(name)

if Relation.new(relation).many?
%w[some none every].each do |suffix|
argument "#{relation_name}_#{suffix}", relation_filter, required: false, camelize: false
end
else
argument relation_name.to_s, relation_filter, required: false, camelize: false
argument relation_name, relation_filter, required: false, camelize: false
end
end
end
Expand Down
12 changes: 3 additions & 9 deletions lib/graphoid/definitions/types.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,30 +4,24 @@ module Graphoid
module Resolvers
def self.resolver_class(relation_class, relation_type, association)
association_name = association.name
relation_name = Utils.graphqlize(relation_class.name)

Class.new(GraphQL::Schema::Resolver) do
type [relation_type], null: true

self.const_set(:ASSOCIATION_NAME, association_name)

filter = Graphoid::Filters::LIST[relation_class]
filter = "Graphoid::Types::#{relation_name}Filter" unless filter
order = Graphoid::Sorter::LIST[relation_class]
order = "Graphoid::Types::#{relation_name}Sorter" unless order

argument :where, filter, required: false
order = Graphoid::Sorter::LIST[relation_class]
order = "Graphoid::Types::#{Utils.graphqlize(relation_class.name)}Sorter" unless order
argument :order, order, required: false
argument :limit, GraphQL::Types::Int, required: false
argument :skip, GraphQL::Types::Int, required: false

def resolve(where: nil, order: nil, limit: nil, skip: nil)
def resolve(order: nil, limit: nil, skip: nil)
obj = self.object
processor = Graphoid::Queries::Processor

association_name = self.class.const_get(:ASSOCIATION_NAME)
result = obj.send(association_name)
result = processor.execute(result, where) if where.present?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No longer executes or validates

records {
  data {
    attachments(where: { type: "picture" }) {
      id
    }
  }
}

However, other operations are still viable

records {
  data {
    attachments(limit: 10, skip: 0, order: { id: ASC }) {
      id
    }
  }
}


if order.present?
order = processor.parse_order(obj.send(association_name), order)
Expand Down
1 change: 1 addition & 0 deletions lib/graphoid/graphoid.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
require 'graphoid/scalars'
require 'graphoid/argument'
require 'graphoid/graphield'
require 'graphoid/mongoid_nested_filter'

require 'graphoid/operators/attribute'
require 'graphoid/operators/relation'
Expand Down
19 changes: 19 additions & 0 deletions lib/graphoid/mongoid_nested_filter.rb

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This tells Mongoid to allow the new type of param in the association

Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# frozen_string_literal: true

module Graphoid
module MongoidNestedFilter
private

def define_association!(macro_name, name, options = {}, &block)
options = options.dup
nested_filter = options.delete(:graphoid_nested_filter)
association = super

# Mongoid validates association options before Graphoid can inspect the reflection.
association.options[:graphoid_nested_filter] = nested_filter if nested_filter
association
end
end
end

Mongoid::Association::Macros::ClassMethods.prepend(Graphoid::MongoidNestedFilter)
2 changes: 1 addition & 1 deletion spec/tester_mongo/app/models/person.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@ class Person
field :camelCase, type: String
field :name, type: String

belongs_to :account
belongs_to :account, graphoid_nested_filter: true
end
101 changes: 6 additions & 95 deletions spec/tester_mongo/spec/graphoid/queries/attributes/subfilter_spec.rb
Original file line number Diff line number Diff line change
@@ -1,34 +1,11 @@
# frozen_string_literal: true

# TODO: should apply submodel filtering on embeeded relations
# TODO: elaborate more complex cases

require 'rails_helper'

describe 'QuerySubFilter', type: :request do
subject { Helper.resolve(self, @action, @query) }

before(:all) do
Person.delete_all
Label.delete_all
Account.delete_all
House.delete_all

@h0 = House.create!(name: 'h0')
@a0 = Account.create!(integer_field: 2, house: @h0)
@p0 = Person.create!(account: @a0, name: 'p0', snake_case: 'snake', camelCase: 'camel')
@l0 = Label.create!(account: @a0, name: 'l0', amount: 2)
@l1 = Label.create!(account: @a0, name: 'l1', amount: 2)
end

after(:all) do
[@h0, @a0, @p0, @l0, @l1].map(&:destroy)
end

describe 'applies filters in related models' do
it 'when has many' do
@action = 'accounts'
@query = %{
describe 'QuerySubFilter' do
describe 'when filtering a selected relation' do
it 'should reject where on a has-many relation' do
errors = TesterMongoSchema.validate(<<~GRAPHQL)
query {
accounts {
id
Expand All @@ -37,75 +14,9 @@
}
}
}
}
GRAPHQL

expect(subject[0]['labels'].size).to eq(1)
expect(subject[0]['labels'][0]['id']).to eq @l0.id.to_s
expect(errors.first.message).to include("Field 'labels' doesn't accept argument 'where'")
end

# DEPRECATED: this is not supported anymore
# it 'when has one and does not match' do
# @action = 'account'
# @query = %{
# query {
# account {
# id
# person(where: { name: "something" }) {
# id
# }
# }
# }
# }

# expect(subject['person']).not_to be
# end

# it 'when has one and matches' do
# @action = 'account'
# @query = %{
# query {
# account {
# id
# person(where: { name: "p0" }) {
# id
# }
# }
# }
# }

# expect(subject['person']['id']).to eq @p0.id.to_s
# end

# it 'when belongs to and does not match' do
# @action = 'account'
# @query = %{
# query {
# account {
# id
# house(where: { name: "something" }) {
# id
# }
# }
# }
# }

# expect(subject['house']).not_to be
# end

# it 'when belongs to and does and matches' do
# @action = 'account'
# @query = %{
# query {
# account {
# id
# house(where: { name: "h0" }) {
# id
# }
# }
# }
# }

# expect(subject['house']['id']).to eq @h0.id.to_s
# end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# frozen_string_literal: true

require 'rails_helper'

RSpec.describe 'when querying generated relations' do
def validation_errors(query)
TesterMongoSchema.validate(query)
end

def expect_argument_not_accepted(query, argument)
error = validation_errors(query).first

expect(error.message).to include("doesn't accept argument '#{argument}'")
end

it 'should allow an explicitly opted-in relation filter' do
errors = validation_errors('{ people(where: { account: { id_not: null } }) { id } }')

expect(errors).to be_empty
end

%w[some none every].each do |operator|
it "should reject a to-many #{operator} relation filter" do
expect_argument_not_accepted(
"{ accounts(where: { labels_#{operator}: { id_not: null } }) { id } }",
"labels_#{operator}"
)
end
end

it 'should reject where on a selected to-many relation' do
expect_argument_not_accepted(
'{ accounts { labels(where: { name: "label" }) { id } } }',
'where'
)
end

it 'should allow scalar filters and unfiltered relation selection' do
errors = validation_errors(<<~GRAPHQL)
{
accounts(where: { stringField: "account" }) {
labels(order: { id: ASC }, limit: 1, skip: 0) { id }
}
}
GRAPHQL

expect(errors).to be_empty
end
end

This file was deleted.

This file was deleted.

Loading