diff --git a/.yardopts b/.yardopts index f954f33..06ac42a 100644 --- a/.yardopts +++ b/.yardopts @@ -1,6 +1,6 @@ --markup markdown --plugin activesupport-concern --exclude lib/generators/* -- lib/**/*.rb +- docs/* diff --git a/lib/fmrest/spyke.rb b/lib/fmrest/spyke.rb index 915d25a..391b8f7 100644 --- a/lib/fmrest/spyke.rb +++ b/lib/fmrest/spyke.rb @@ -8,6 +8,29 @@ module FmRest module Spyke + class << self + # Sets the bahavior to use when creating an ORM query that can't be + # logically satisified. See the section on querying in the README for + # more info. + # + # Possible values: + # + # * `:raise` - Raise an `FmRest::Spyke::UnsatisfiableQuery` exception + # (inherits from `ArgumentError`) when the unsatisifiable query is + # created + # * `:request_silent` - Silently allow the unsatisifiable query to go + # through, which will be translated in the Data API as `field: + # "1001..1000"` (ensures zero results) + # * `:return_silent` - Silently return an empty resultset without issuing + # a request to the Data API. Use this option if you don't care about + # potential server-side side effects (e.g. scripts) of running a query. + # * `:return_warn` - Same as `:return_silent` but will `warn()` about the + # unsatisifiable query + # * `:request_warn`/`nil` (default) - Same as `:request_silent`, but will + # `warn()` about the unsatisifiable query + attr_accessor :on_unsatisifiable_query + end + def self.included(base) base.include Model end diff --git a/lib/fmrest/spyke/model/orm.rb b/lib/fmrest/spyke/model/orm.rb index 4b0681e..792ac86 100644 --- a/lib/fmrest/spyke/model/orm.rb +++ b/lib/fmrest/spyke/model/orm.rb @@ -48,7 +48,7 @@ def all def fetch(options = {}) if current_scope.has_query? scope = extend_scope_with_fm_params(current_scope, prefixed: false) - scope = scope.where(query: scope.query_params) + scope = extend_scope_with_query_params(scope) scope = scope.with(FmRest::V1::find_path(layout)) else scope = extend_scope_with_fm_params(current_scope, prefixed: true) @@ -56,15 +56,16 @@ def fetch(options = {}) previous, self.current_scope = current_scope, scope - # The DAPI returns a 401 "No records match the request" error when - # nothing matches a _find request, so we need to catch it in order - # to provide sane behavior (i.e. return an empty resultset) - begin - current_scope.has_query? ? scoped_request(:post) : super() - rescue FmRest::APIError::NoMatchingRecordsError => e - raise e if options[:raise_on_no_matching_records] - ::Spyke::Result.new({}) - end + current_scope.has_query? ? scoped_request(:post) : super() + + # The DAPI returns a 401 "No records match the request" error when + # nothing matches a _find request, so we need to catch it in order + # to provide sane behavior (i.e. return an empty resultset) + rescue FmRest::APIError::NoMatchingRecordsError => e + raise e if options[:raise_on_no_matching_records] + ::Spyke::Result.new({}) + rescue Relation::UnsatisfiableQuery + ::Spyke::Result.new({}) ensure self.current_scope = previous end @@ -80,6 +81,17 @@ def create!(attributes = {}) private + def extend_scope_with_query_params(scope) + query_params = scope.query_params + + case FmRest::Spyke.on_unsatisifiable_query + when :return_silent, 'return_silent', :return_warn, 'return_warn' + query_params = scope.satisifiable_query_params + end + + scope.where(query: query_params) + end + def extend_scope_with_fm_params(scope, prefixed: false) prefix = prefixed ? "_" : nil diff --git a/lib/fmrest/spyke/relation.rb b/lib/fmrest/spyke/relation.rb index f8a9c3b..2d54395 100644 --- a/lib/fmrest/spyke/relation.rb +++ b/lib/fmrest/spyke/relation.rb @@ -18,6 +18,7 @@ def u.to_s; ZERO_RESULTS_QUERY; end NORMALIZED_OMIT_KEY = 'omit' class UnknownQueryKey < ArgumentError; end + class UnsatisfiableQuery < ArgumentError; end # NOTE: We need to keep limit, offset, sort, query and portal accessors # separate from regular params because FM Data API uses either "limit" or @@ -467,6 +468,15 @@ def find_each(batch_size: 1000) end end + def satisifiable_query_params(raise_on_empty: true) + params = self.query_params = + query_params.reject { |q| q.value?(UNSATISFIABLE_QUERY_VALUE) } + + raise UnsatisfiableQuery if params.empty? && raise_on_empty + + params + end + protected def set_portal_params(params_hash, param) @@ -504,13 +514,32 @@ def cartesian_product_query_params(params) def unsatisfiable(field, a, b) unless a == UNSATISFIABLE_QUERY_VALUE || b == UNSATISFIABLE_QUERY_VALUE - # TODO: Add a setting to make this an exception instead of a warning? - warn( - "An FmRest query using `and' required that `#{field}' match " \ - "'#{a}' and '#{b}' at the same time which can't be satisified. " \ - "This will appear in the find request as '#{UNSATISFIABLE_QUERY_VALUE}' " \ - "and may result in an empty resultset." - ) + case FmRest::Spyke.on_unsatisifiable_query + when :request_silent, :return_silent, 'request_silent', 'return_silent' + when :raise, 'raise' + raise ArgumentError, + "An FmRest query using `.and' required that '#{field}' match " \ + "'#{a}' & '#{b}' at the same time which can't be satisified." + when :return_warn, 'return_warn' + warn( + "An FmRest query using `.and' required that '#{field}' match " \ + "'#{a}' & '#{b}' at the same time which can't be satisified. " \ + "The conflicting part of the query will be omitted from the " \ + "request, and no Data API request will be sent if there's " \ + "nothing left to query for." + ) + when :request_warn, 'request_warn', nil + warn( + "An FmRest query using `.and' required that '#{field}' match " \ + "'#{a}' & '#{b}' at the same time which can't be satisified. " \ + "This will appear in the find request as '#{UNSATISFIABLE_QUERY_VALUE}' " \ + "and may result in an empty resultset." + ) + else + raise ArgumentError, "Unrecognized value for " \ + "FmRest::Spyke.on_unsatisifiable_query " \ + "`#{FmRest::Spyke.on_unsatisifiable_query}'" + end end UNSATISFIABLE_QUERY_VALUE