[ Web Proxy ]
URL:
Viewing: https://raw.githubusercontent.com/contentstack/contentstack-ruby/master/lib/contentstack/query.rb [Back]  [Original]

require 'contentstack/entry_collection'
require 'util'

module Contentstack
  # A class that defines a query that is used to query for Entry instance.
  class Query
    using Utility
    # @!attribute [r] query
    #   Attribute which has all the information about the query which will be executed against Contentstack API

    # @!attribute [r] content_type
    #   Denotes which `content_type` should the query be executed for

    attr_reader :query, :content_type

    # Initialize the Query instance
    # @param [String] content_type
    #
    # Example:
    #    @query   = @stack.content_type('blog').query
    #    @entries = @query.where('author', 'John Doe').fetch
    #
    # @return [Contentstack::Query]
    def initialize(content_type)
      @content_type = content_type
      @query = {
        query: "{}",
        include_count: false,
        skip: 0,
        count: 10,
        desc: 'created_at'
      }
    end

    # Add a custom query against specified key.
    # @param [String] field_uid
    # @param [String/Number/Boolean/Hash] value
    #
    # Example:
    #    @query   = @stack.content_type('blog').query
    #    @query.add_query('author', "Jane Doe")
    #
    # @return [Contentstack::Query]
    def add_query(field_uid, value)
      add_query_hash({:"#{field_uid}" => value})
    end

    # Remove provided query key from custom query if exist.
    # @param [String] field_uid
    #
    # Example:
    #    @query   = @stack.content_type('blog').query
    #    @query.remove_query('author')
    #
    # @return [Contentstack::Query]
    def remove_query(field_uid)
      q = ActiveSupport::JSON.decode(@query[:query])
      q.delete(field_uid)
      @query[:query] = ActiveSupport::JSON.encode(q)
      self
    end

    # Add a constraint to fetch all entries that contains given value against specified key.
    # @param [Hash] query_hash
    #
    # Example:
    #    @query = @stack.content_type('blog').query
    #    @query.where({:author => "Jane Doe"})
    #
    # @return [Contentstack::Query]
    def where(query_hash)
      add_query_hash(query_hash)
    end

    # Add a regular expression constraint for finding string values that match the provided regular expression. This may be slow for large data sets.
    # @param [String] field_uid  The key to be constrained.
    # @param [String] pattern  The regular expression pattern to match.
    # @param [String] options  Regex options
    #
    # Example:
    #    @query = @stack.content_type('product').query
    #    @query.regex('title', '.*Mobile.*', 'i') # Search without case sensitivity
    #
    # @return [Contentstack::Query]
    def regex(field_uid, pattern, options="")
      hash = {
        "#{field_uid}" => {
          "$regex": pattern
        }
      }

      hash["#{field_uid}"]["$options"] = options if !options.empty? || !options.nil?

      add_query_hash(hash)
    end

    # Add a constraint that requires, a specified key exists in response.
    # @param [String] field_uid The key to be constrained.
    #
    # Example:
    #    @query = @stack.content_type('product').query
    #    @query.exists?('product_image') # only fetch products which have a `product_image`
    #
    # @return [Contentstack::Query]
    def exists?(field_uid)
      add_query_hash({:"#{field_uid}" => {"$exists" => true}})
    end

    # Add a constraint that requires, a specified key does not exists in response.
    # @param [String] field_uid The key to be constrained.
    #
    # Example:
    #    @query = @stack.content_type('product').query
    #    @query.not_exists?('product_image') # only fetch products which do not have a `product_image`
    #
    # @return [Contentstack::Query]
    def not_exists?(field_uid)
      add_query_hash({:"#{field_uid}" => {"$exists" => false}})
      self
    end

    # Combines all the queries together using AND operator.
    #
    # @param [Array] queries  Array of instances of the Query class
    #
    # Each query should be an instance of the Contentstack::Query class, and belong to the same `content_type`
    # Example:
    #    @query1 = @stack.content_type('category').query
    #    @query1.where('title', 'Electronics')
    # 
    #    @query2 = @stack.content_type('category').query
    #    @query2.regex('description', '.*Electronics.*')
    #
    #    query_array = [@query1, @query2]
    #
    #    @query = @stack.content_type('category').query
    #    @query.and(query_array)
    #
    # @return [Contentstack::Query]
    def and(queries)
      add_query_hash({"$and" => concat_queries(queries)})
      self
    end

    # Combines all the queries together using OR operator.
    #
    # @param [Array] queries  Array of instances of the Query class
    #
    # Each query should be an instance of the Contentstack::Query class, and belong to the same `content_type`
    # Example:
    #    @query1 = @stack.content_type('category').query
    #    @query1.where('title', 'Electronics')
    # 
    #    @query2 = @stack.content_type('category').query
    #    @query2.where('title', 'Apparel')
    #
    #    query_array = [@query1, @query2]
    #
    #    @query = @stack.content_type('category').query
    #    @query.or(query_array)
    #
    # @return [Contentstack::Query]
    def or(queries)
      add_query_hash({"$or" => concat_queries(queries)})
      self
    end

    # Add a constraint to the query that requires a particular key entry to be less than the provided value.
    #
    # @param [String] field_uid  UID of the field for which query should be executed
    # 
    # @param [String/Number] value  Value that provides an upper bound
    # 
    # Example
    #    @query = @stack.content_type('product').query
    #    @query.less_than('price', '100')
    #
    # @return [Contentstack::Query]
    def less_than(field_uid, value)
      add_query_hash({:"#{field_uid}" => {"$lt" => value}})
      self
    end

    # Add a constraint to the query that requires a particular key entry to be less than or equal to the provided value.
    #
    # @param [String] field_uid  UID of the field for which query should be executed
    # 
    # @param [String/Number] value  Value that provides an upper bound
    # 
    # Example
    #    @query = @stack.content_type('product').query
    #    @query.less_than_or_equal('price', '100')
    #
    # @return [Contentstack::Query]
    def less_than_or_equal(field_uid, value)
      add_query_hash({:"#{field_uid}" => {"$lte" => value}})
      self
    end

    # Add a constraint to the query that requires a particular key entry to be greater than the provided value.
    #
    # @param [String] field_uid  UID of the field for which query should be executed
    # 
    # @param [String/Number] value  Value that provides a lower bound
    # 
    # Example
    #    @query = @stack.content_type('product').query
    #    @query.greater_than('price', '100')
    #
    # @return [Contentstack::Query]
    def greater_than(field_uid, value)
      add_query_hash({:"#{field_uid}" => {"$gt" => value}})
      self
    end

    # Add a constraint to the query that requires a particular key entry to be greater than or equal to the provided value.
    #
    # @param [String] field_uid UID of the field for which query should be executed
    # 
    # @param [String/Number] value Value that provides a lower bound
    # 
    # Example
    #    @query = @stack.content_type('product').query
    #    @query.greater_than_or_equal('price', '100')
    #
    # @return [Contentstack::Query]
    def greater_than_or_equal(field_uid, value)
      add_query_hash({:"#{field_uid}" => {"$gte" => value}})
      self
    end

    # Add a constraint to the query that requires a particular key's entry to be not equal to the provided value.
    #
    # @param [String] field_uid UID of the field for which query should be executed
    # @param [String] value The object that must not be equaled.
    #
    # Example
    #    @query = @stack.content_type('product').query
    #    @query.not_equal_to('price', '100')
    #
    # @return [Contentstack::Query]
    def not_equal_to(field_uid, value)
      add_query_hash({:"#{field_uid}" => {"$ne" => value}})
      self
    end

    # Add a constraint to the query that requires a particular key's entry to be contained in the provided array.
    #
    # @param [String] field_uid UID of the field for which query should be executed
    # @param [String] values The possible values for the key's object
    #
    # Example 1 - Array Equals Operator Within Group
    #    @query = @stack.content_type('category').query
    #    @query.contained_in("title", ["Electronics", "Apparel"])
    #
    # Example 2 - Array Equals Operator Within Modular Blocks
    #    @query = @stack.content_type('category').query
    #    @query.contained_in("additional_info.deals.deal_name", ["Christmas Deal", "Summer Deal"])
    #
    # @return [Contentstack::Query]
    def contained_in(field_uid, values)
      add_query_hash({:"#{field_uid}" => {"$in" => values}})
      self
    end

    # Add a constraint to the query that requires a particular key entry's value not be contained in the provided array.
    #
    # @param [String] field_uid UID of the field for which query should be executed
    # @param [String] values The possible values for the key's object
    #
    # Example 1 - Array Not-equals Operator Within Group
    #    @query = @stack.content_type('category').query
    #    @query.not_contained_in("title", ["Electronics", "Apparel"])
    #
    # Example 2 - Array Not-equals Operator Within Modular Blocks
    #    @query = @stack.content_type('category').query
    #    @query.not_contained_in("additional_info.deals.deal_name", ["Christmas Deal", "Summer Deal"])
    #
    # @return [Contentstack::Query]
    def not_contained_in(field_uid, values)
      add_query_hash({:"#{field_uid}" => {"$nin" => values}})
      self
    end

    # The number of objects to skip before returning any.
    #
    # @param [Number] count of objects to skip from resulset.
    #
    # Example
    #    @query = @stack.content_type('category').query
    #    @query.skip(50)
    #
    # @return [Contentstack::Query]
    def skip(count)
      @query[:skip] = count
      self
    end

    # This method provides only the entries matching the specified value.
    # @deprecated since version 0.5.0
    # @param [String] text value used to match or compare
    #
    # Example
    #    @query = @stack.content_type('product').query
    #    @query.search("This is an awesome product")
    #
    # @return [Contentstack::Query]
    def search(text)
      @query[:typeahead] = text
      self
    end

    # A limit on the number of objects to return.
    #
    # @param [Number] count of objects to limit in resulset.
    #
    # Example
    #    @query = @stack.content_type('category').query
    #    @query.limit(50)
    #
    # @return [Contentstack::Query]
    def limit(count=10)
      @query[:limit] = count
      self
    end

    # Retrieve only count of entries in result.
    #
    # Example
    #    @query = @stack.content_type('category').query
    #    @query.count
    #
    # @return [Integer]
    def count
      include_count
      fetch.count
    end

    # Retrieve count and data of objects in result.
    #
    # Example
    #    @query = @stack.content_type('category').query
    #    @query.include_count
    #
    # @return [Contentstack::Query]
    def include_count(flag=true)
      @query[:include_count] = flag
      self
    end

    # Retrieve count and data of objects in result.
    #
    # Example
    #    @query = @stack.content_type('category').query
    #    @query.include_metadata
    #
    # @return [Contentstack::Query]
    def include_metadata(flag=true)
      @query[:include_metadata] = flag
      self
    end

    # Sort the results in ascending order with the given key. 
    # Sort the returned entries in ascending order of the provided key.
    #
    # @param [String] field_uid The key to order by
    #
    # Example
    #    @query = @stack.content_type('category').query
    #    @query.ascending
    #
    # @return [Contentstack::Query]
    def ascending(field_uid)
      @query.delete(:desc)
      @query[:asc] = field_uid
      self
    end

    # Sort the results in descending order with the given key. 
    # Sort the returned entries in descending order of the provided key.
    #
    # @param [String] field_uid The key to order by
    #
    # Example
    #    @query = @stack.content_type('category').query
    #    @query.descending
    #
    # @return [Contentstack::Query]
    def descending(field_uid)
      @query.delete(:asc)
      @query[:desc] = field_uid
      self
    end

    # Get entries from the specified locale. 
    #
    # @param [String] code The locale code of the entry
    #
    # Example
    #    Change language method
    #    @query = @stack.content_type('category').query
    #    @query.locale('en-us')
    #
    # @return [Contentstack::Query]
    def locale(code)
      @query[:locale] = code
      self
    end

    # Specifies an array of 'only' keys in BASE object that would be 'included' in the response.
    #
    # @param [Array] fields Array of the 'only' reference keys to be included in response.
    # @param [Array] fields_with_base Can be used to denote 'only' fields of the reference class
    #
    # Example
    #    # Include only title and description field in response
    #    @query = @stack.content_type('category').query
    #    @query.only(['title', 'description'])
    #
    #    # Query product and include only the title and description from category reference
    #    @query = @stack.content_type('product').query
    #    @query.include_reference('category')
    #          .only('category', ['title', 'description'])
    #
    # @return [Contentstack::Query]
    def only(fields, fields_with_base=nil)
      q = {}
      if [Array, String].include?(fields_with_base.class)
        fields_with_base = [fields_with_base] if fields_with_base.class == String
        q[fields.to_sym] = fields_with_base
      else
        fields = [fields] if fields.class == String
        q = {BASE: fields}
      end

      @query[:only] = q
      self
    end

    # Specifies list of field uids that would be 'excluded' from the response.
    #
    # @param [Array] fields Array of field uid which get 'excluded' from the response.
    # @param [Array] fields_with_base Can be used to denote 'except' fields of the reference class
    #
    # Example
    #    # Exclude 'description' field in response
    #    @query = @stack.content_type('category').query
    #    @query.except(['description'])
    #
    #    # Query product and exclude the 'description' from category reference
    #    @query = @stack.content_type('product').query
    #    @query.include_reference('category')
    #          .except('category', ['description'])
    #
    # @return [Contentstack::Query]
    def except(fields, fields_with_base=nil)
      q = {}
      if [Array, String].include?(fields_with_base.class)
        fields_with_base = [fields_with_base] if fields_with_base.class == String
        q[fields.to_sym] = fields_with_base
      else
        fields = [fields] if fields.class == String
        q = {BASE: fields}
      end

      @query[:except] = q
      self
    end

    # Add a constraint that requires a particular reference key details.
    #
    # @param [String/Array] reference_field_uids Pass string or array of reference fields that must be included in the response
    #
    # Example
    #
    #    # Include reference of 'category'
    #    @query = @stack.content_type('product').query
    #    @query.include_reference('category')
    #
    #    # Include reference of 'category' and 'reviews'
    #    @query = @stack.content_type('product').query
    #    @query.include_reference(['category', 'reviews'])
    #
    # @return [Contentstack::Query]
    def include_reference(reference_field_uids)
      self.include(reference_field_uids)
    end

    # Include schemas of all returned objects along with objects themselves.
    #
    # Example
    #
    #    @query = @stack.content_type('product').query
    #    @query.include_schema
    #
    # @return [Contentstack::Query]
    def include_schema(flag=true)
      @query[:include_schema] = flag
      self
    end

    # Include object owner's profile in the objects data.
    #
    # Example
    #
    #    @query = @stack.content_type('product').query
    #    @query.include_owner
    #
    # @return [Contentstack::Query]
    def include_owner(flag=true)
      @query[:include_owner] = flag
      self
    end

    # Include object's content_type in response
    #
    # Example
    #
    #    @query = @stack.content_type('product').query
    #    @query.include_content_type
    #
    # @return [Contentstack::Query]
    def include_content_type(flag=true)
      @query[:include_content_type] = flag
      self
    end


    # Include the fallback locale publish content, if specified locale content is not publish.
    #
    # Example
    #
    #    @query = @stack.content_type('product').query
    #    @query.include_fallback
    #
    # @return [Contentstack::Query]
    def include_fallback(flag=true)
      @query[:include_fallback] = flag
      self
    end

    # Include the branch for publish content.
    #
    # Example
    #
    #    @query = @stack.content_type('product').query
    #    @query.include_branch
    #
    # @return [Contentstack::Entry]
    def include_branch(flag=true)
      @query[:include_branch] = flag
      self
    end

    # Scope the entries request to one or more entry variants, optionally on a branch.
    #
    # @param [String, Array] variant_uids A variant UID, or an array of variant UIDs
    # @param [String] branch_name Branch name to scope the request (overrides stack-level branch)
    #
    # Example
    #
    #    @query = @stack.content_type('home_page').query
    #    @query.variants('xyz', 'branch_name').fetch
    #
    #    @query = @stack.content_type('home_page').query
    #    @query.variants(['variant1', 'variant2'], 'branch_name').fetch
    #
    # @return [Contentstack::Query]
    def variants(variant_uids, branch_name = nil)
      API.validate_variant_uids!(variant_uids)
      @query[:variant_uids] = variant_uids
      @query[:branch] = branch_name if branch_name.is_a?(String) && !branch_name.empty?
      self
    end

    # Include Embedded Objects (Entries and Assets) along with entry/entries details.
    #
    # Example
    #
    #    @query = @stack.content_type('product').query
    #    @query.include_embedded_items
    #
    # @return [Contentstack::Query]
    def include_embedded_items()
      @query[:include_embedded_items] = ['BASE']
      self
    end

    # @deprecated since 0.8.5 The Content Delivery API returns published content only.
    #   Unpublished or draft entries are not available through CDA queries. Use Live Preview
    #   with the Preview Service, or the Content Management API, to access unpublished content.
    #
    # @return [Contentstack::Query]
    def include_draft(_flag=true)
      warn(
        "Contentstack: Query#include_draft is deprecated and has no effect on the Content " \
        "Delivery API, which returns published content only. To preview unpublished entries, " \
        "use Live Preview with the Preview Service. To manage or fetch draft entries, use " \
        "the Content Management API.",
        uplevel: 1
      )
      self
    end


    #
    # @return [Contentstack::Query]
    def include(field_uids)
      field_uids = [field_uids] if field_uids.class == String
      @query[:include] ||= []
      @query[:include] = @query[:include] | field_uids
      self
    end

    # Include tags with which to search entries.
    #
    # @param [Array] tags_array Array of tags using which search must be performed
    #
    # Example
    #
    #    @query = @stack.content_type('product').query
    #    @query.tags(["tag1", "tag2"])
    #
    # @return [Contentstack::Query]
    def tags(tags_array)
      @query[:tags] = tags_array
      self
    end


    # Execute query
    #
    # Example
    #
    #    @query = @stack.content_type('product').query
    #    @query.tags(["tag1", "tag2"])
    #          .fetch
    #
    # @return [Contentstack::EntryCollection]
    def fetch
      entries = API.fetch_entries(@content_type, @query)
      EntryCollection.new(entries, @content_type)
    end

    # Execute a Query and get the single matching object
    #
    # Example
    #
    #    @query = @stack.content_type('product').query
    #    @query.tags(["tag1", "tag2"])
    #          .find_one
    #
    # @return [Contentstack::Entry]
    def find_one
      limit 1
      fetch.first
    end

    alias_method :find, :fetch
    alias_method :in, :contained_in
    alias_method :not_in, :not_contained_in

    private
    def add_query_hash(query_hash)
      q = ActiveSupport::JSON.decode(@query[:query])
      q.merge!(query_hash)
      @query[:query] = ActiveSupport::JSON.encode(q)
      self
    end

    def concat_queries(queries)
      this_queries = []
      this_query = ActiveSupport::JSON.decode(@query[:query])
      if this_query.keys.length > 0
        this_queries = [this_query]
      end

      if queries.class == Array
        queries.map do |query_object|
          if query_object.class == Contentstack::Query && query_object.content_type == @content_type
            q = ActiveSupport::JSON.decode(query_object.query[:query])
            this_queries.push(q.symbolize_keys)
          end
        end
      end

      this_queries
    end
  end
end

Web Proxy Viewer  |  New URL  |  Original Page