lib/my_gists/search.rb
module MyGists
# Public: Search tags, or search gists by tag and profile. Respond with an
# Enumerable which wraps an Array of decorated Gists or Tags.
#
# Examples
#
# MyGists::Search.for(:tags)
#
# MyGists::Search.for(:tags, page: 1)
#
# MyGists::Search.for(:tags, page: 1, private: false)
#
# MyGists::Search.for(:tags, page: 1, profile: profile)
#
# MyGists::Search.for(:tags, page: 1, profile: profile, private: false)
#
# MyGists::Search.for(:gists)
#
# MyGists::Search.for(:gists, page: 1)
#
# MyGists::Search.for(:gists, page: 1, private: false)
#
# MyGists::Search.for(:gists, page: 1, tag: tag)
#
# MyGists::Search.for(:gists, page: 1, tag: tag, private: false)
#
# MyGists::Search.for(:gists, page: 1, tag: tag, profile: profile)
#
# MyGists::Search.for(:gists, page: 1, tag: tag, profile: profile, private: false)
class Search
include Enumerable
# Public: Expose Kaminari API.
delegate :current_page, :total_pages, :limit_value, to: :scope
class << self
# Internal: Only Search.for can initialize a new Search.
protected :new
end
# Public: Search for either gists or tags. Refine search results by
# options such as tag, profile and status. Search result set is a
# collection of decorated gists or tags.
#
# resource - The Symbol of which resource to search for. The Symbol should
# be either :gists or :tags.
# options - The Hash options used to refine the search (default: {}):
# :tag - Tag or String of tag name (optional).
# :page - Integer for page of collection (optional).
# :profile - Profile or String who owns resources (optional).
# :private - TrueClass or FalseClass used to exclude private
# resources from search results (optional).
#
# Examples
#
# MyGists::Search.for(:tags, profile: profile).to_a
# # => [#<ActsAsTaggableOn::Tag id: 183, name: "JavaScript", slug: "javascript">,
# #<ActsAsTaggableOn::Tag id: 182, name: "Without Tags", slug: "without-tags">]
#
# MyGists::Search.for(:gists, tag: tag, profile: profile).to_a
# # => [#<Gist id: 502, description: "Delete all notifications button #github"...>]
#
# Returns an Enumerable which wraps an Array of decorated Gists or Tags.
def self.for(resource, options = {})
new(options) { send(resource) }
end
# Public: Initialize a Search.
#
# options - The Hash options used to refine the search:
# :tag - Tag or String of tag name (optional).
# :page - Integer for page of collection (optional).
# :profile - Profile who owns resources (optional).
# :private - TrueClass or FalseClass used to exclude private
# resources from search results (optional).
#
# Yields within context of self.
def initialize(options, &block)
@page = options.delete(:page)
@include_private = options.fetch(:private, true)
profile_option = options.delete(:profile)
@profile = if profile_option.is_a?(String) && profile_option.present?
# When a profile search term exists, we only want to
# perform a search if we find a Profile. If we don't find
# a Profile, an empty Profile instance is used in the
# scope, and no gists will be found.
Profile.find_or_initialize_by_username(profile_option)
else
profile_option
end
tag_option = options.delete(:tag)
@tag_name = tag_option.respond_to?(:name) ? tag_option.name : tag_option
instance_eval(&block) if block_given?
end
# Public: Method required by Enumerable. Iterates resources found by the
# scope and decorates each resource.
#
# Yields a decorated resource.
def each(&block)
scope.each { |resource| block.call(resource.decorate) }
end
# Public: Whether or not our search result set is empty.
#
# Returns a TrueClass or FalseClass depending on existence of result set.
def empty?
scope.size == 0
end
private
# Internal: Returns the Integer for page of collection from received options.
attr_reader :page
# Internal: Returns the Profile from received options.
attr_reader :profile
# Internal: Returns the TrueClass or FalseClass used to exclude private
# resources from search results.
attr_reader :include_private
# Internal: Returns the String tag name from received options.
attr_reader :tag_name
# Internal: Returns the current ActiveRecord query object.
attr_reader :scope
# Internal: Build a tags scope based on profile, tag_name and
# include_private attributes.
#
# Returns nothing.
def tags
@scope = MyGists::Search::Scope::Tags.by(profile: profile,
tag_name: tag_name)
@scope = @scope.only_public unless include_private
end
# Internal: Build a gists scope based on profile, tag_name and
# include_private attributes.
#
# Returns nothing.
def gists
@scope = MyGists::Search::Scope::Gists.by(profile: profile,
tag_name: tag_name)
@scope = @scope.only_public unless include_private
@scope = @scope.page(page)
end
end
end