diff --git a/lib/graphiti.rb b/lib/graphiti.rb index cd419112..87f1c893 100644 --- a/lib/graphiti.rb +++ b/lib/graphiti.rb @@ -143,7 +143,6 @@ def self.cache(name, kwargs = {}, &block) require "graphiti/request_validator" require "graphiti/request_validators/validator" require "graphiti/request_validators/update_validator" -require "graphiti/query" require "graphiti/scope" require "graphiti/deserializer" require "graphiti/renderer" @@ -182,6 +181,7 @@ def self.cache(name, kwargs = {}, &block) require "graphiti/extensions/boolean_attribute" require "graphiti/extensions/temp_id" require "graphiti/serializer" +require "graphiti/query" require "graphiti/debugger" if defined?(ActiveRecord) diff --git a/lib/graphiti/query.rb b/lib/graphiti/query.rb index b0511ece..1b59ccbe 100644 --- a/lib/graphiti/query.rb +++ b/lib/graphiti/query.rb @@ -78,11 +78,14 @@ def sideload_hash end end + class RemoteSideloadResource < ::Graphiti::Resource + self.remote = "_remote_sideload_".freeze + self.abstract_class = true # exclude from schema + end + def resource_for_sideload(sideload) if @resource.remote? - Class.new(Graphiti::Resource) { - self.remote = "_remote_sideload_" - }.new + RemoteSideloadResource.new else sideload.resource end diff --git a/lib/graphiti/resource/dsl.rb b/lib/graphiti/resource/dsl.rb index f33be74c..8d073b8f 100644 --- a/lib/graphiti/resource/dsl.rb +++ b/lib/graphiti/resource/dsl.rb @@ -205,7 +205,7 @@ def relationship_option(options, name) options[name] ||= send(:"relationships_#{name}_by_default") end end - private :attribute_option + private :relationship_option end end end diff --git a/lib/graphiti/schema.rb b/lib/graphiti/schema.rb index 835cfb57..7b08b2ad 100644 --- a/lib/graphiti/schema.rb +++ b/lib/graphiti/schema.rb @@ -42,7 +42,7 @@ def generate def generate_types {}.tap do |types| - Graphiti::Types.map.each_pair do |name, config| + Graphiti::Types.map.sort.each_entry do |name, config| types[name] = config.slice(:kind, :description) end end diff --git a/lib/graphiti/scoping/filter.rb b/lib/graphiti/scoping/filter.rb index 9ab425f1..9d75feef 100644 --- a/lib/graphiti/scoping/filter.rb +++ b/lib/graphiti/scoping/filter.rb @@ -193,14 +193,14 @@ def parse_string_arrays(value, singular_filter) # Find the quoted strings quotes = value.scan(/{{.*?}}/) # remove them from the rest - quotes.each { |q| value.gsub!(q, "") } + non_quotes = quotes.inject(value) { |v, q| v.gsub(q, "") } # remove the quote characters from the quoted strings quotes.each { |q| q.gsub!("{{", "").gsub!("}}", "") } # merge everything back together into an array value = if singular_filter - Array(value) + quotes + Array(non_quotes) + quotes else - Array(value.split(",")) + quotes + Array(non_quotes.split(",")) + quotes end # remove any blanks that are left value.reject! { |v| v.length.zero? } diff --git a/spec/filtering_spec.rb b/spec/filtering_spec.rb index a47f2619..613aa947 100644 --- a/spec/filtering_spec.rb +++ b/spec/filtering_spec.rb @@ -29,6 +29,20 @@ def self.name expect(records.map(&:id)).to eq([employee1.id]) end + context "retains filtering value" do + it "when value includes curly brackets" do + params[:filter] = {first_name: "{{John}}"} + records + expect(params[:filter]).to eq(first_name: "{{John}}") + end + + it "when value does not include curly brackets" do + params[:filter] = {first_name: "John"} + records + expect(params[:filter]).to eq(first_name: "John") + end + end + context "when filter is type hash" do before do resource.filter :by_json, :hash do diff --git a/spec/query_spec.rb b/spec/query_spec.rb index 7e7b99a1..85c3d279 100644 --- a/spec/query_spec.rb +++ b/spec/query_spec.rb @@ -11,6 +11,7 @@ before do employee_resource.has_many :positions, resource: position_resource + employee_resource.belongs_to :remote, remote: true position_resource.belongs_to :department, resource: department_resource employee_resource.attribute :name, :string @@ -986,11 +987,29 @@ end describe "sideloads" do - before { params[:include] = "positions" } subject(:sideloads) { instance.sideloads } - it "does not cascate the action" do - expect(sideloads.values.map(&:action)).to eq([:all]) + context "when including an has_many resource" do + before { params[:include] = "positions" } + + it "does not cascate the action" do + expect(sideloads.values.map(&:action)).to eq([:all]) + end + end + + context "when including a resource from a remote resource" do + before { params[:include] = "remote.resource" } + + let(:sideloads_of_another_query) { described_class.new(resource, params).sideloads } + + def resource_class_of_remote_sideload(sideloads) + sideloads.fetch(:remote).sideloads.fetch(:resource).resource.class + end + + it "re-uses resource class across multiple queries (avoid memory leak)" do + expect(resource_class_of_remote_sideload(sideloads)) + .to eq(resource_class_of_remote_sideload(sideloads_of_another_query)) + end end end end diff --git a/spec/schema_spec.rb b/spec/schema_spec.rb index a27fc1cd..9d87778c 100644 --- a/spec/schema_spec.rb +++ b/spec/schema_spec.rb @@ -244,6 +244,10 @@ def self.name expect(schema[:types]).to eq(expected[:types]) end + it "has sorted types" do + expect(schema[:types].to_a).to eq(expected[:types].sort) + end + # Dynamically-created resources, e.g. remote resources context "when resource has missing name" do let(:no_name) do