Skip to content

Instantly share code, notes, and snippets.

@brennovich
Forked from tomas-stefano/solr.rb
Created March 5, 2014 21:12
Show Gist options
  • Save brennovich/9376694 to your computer and use it in GitHub Desktop.
Save brennovich/9376694 to your computer and use it in GitHub Desktop.
module Morpheus
module Adapters
module Solr
class Relation
include ActiveModel::Model
attr_accessor :connection, :relation_class, :where
delegate :present?, :blank?, :empty?, to: :to_a
delegate :each, :map, :collect, :select, :find, :last, :first, to: :to_a
delegate :total, :facet_fields, to: :response
# @api public
#
# Define the rows size to pass to Solr.
#
# @param per_page_number the page number that will be calculated to pass in the start Solr option.
#
# @example
#
# Relation.new.per_page(50)
# Relation.new.limit(100)
#
def per_page(per_page_number)
@per_page = per_page_number
self
end
alias :limit :per_page
# @api public
#
# Define the page number to pass to Solr.
#
# @param page_number the string that will pass to solr.
#
# @example
#
# Relation.new.page('2')
#
def page(page_number)
@page = page_number
self
end
# @api public
#
# Define the order column to pass to Solr.
#
# @param order_by the string that will pass to solr.
#
# @example
#
# Relation.new.order('')
#
def order(order_by)
@order = order_by
self
end
# @api public
#
# Define the field query param to send to Solr.
#
# @param field_query the exact field query, that will be send in params.
#
# @example
#
# Relation.new.filter(name: 'Saraiva')
#
def filter(field_query)
@filter = field_query
self
end
# @api public
#
# Change the records parser, to only parse by the FacetResponseHandler.
#
def facets
@facets = true
self
end
# @api public
#
# Returns the start param to send to Solr.
#
# @example
#
# Relation.new.start
#
def start
if @per_page.present? and @page.present?
@per_page.to_i * (@page.to_i - 1)
end
end
# @api public
#
# Returns the total description and the actual records from this current page.
#
# <b>The per page default is 50 and the page default is 1.</b>
#
# @example
#
# Relation.new(connection: connection, relation_class: Solr::Category).page(2).total_description
#
# def total_description
# limit_number = @per_page || 25
# page_number = @page || 1
# initial = start.to_i + 1
# final = limit_number.to_i * page_number.to_i
# final = total if final > total
#
# I18n.t('admin.navigation.total_description', initial: initial, final: final, total: total)
# end
# @api public
#
# Make the solr request and setup the document collection to relation object
#
# @return <Array>
#
# @example
#
# Relation.new(connection: connection, relation_class: Solr::Category).to_a
#
def to_a
return collection if collection.present?
response.records.each { |record| collection.push(record) }
collection
rescue RuntimeError, RSolr::Error::Http => exception
Rails.logger.error exception.message
collection
end
# @api public
#
# Returns the proxy object that wraps the response from Solr, passing the relation class.
# See Solr::ResponseHandler for more information.
#
def response
@response ||= if @facets.present?
Solr::FacetResponseHandler.new(relation_class, response: response_body)
else
Solr::ResponseHandler.new(relation_class, response: response_body)
end
end
# @api public
#
# Returns the Solr Raw response for wt ruby.
#
# <b>You need to pass a connection that responds to #get method.</b>
#
def response_body
Rails.logger.info("\e[36m #{relation_class} Load #{connection.base_uri.path} \e\[0m \e[01m#{params}\e[0m")
connection.get('select', params: params)
end
# @api public
#
# Returns all collection array from solr.
#
def collection
@collection ||= []
end
def inspect
"#<Solr::Relation #{to_a}>"
end
# @api private
#
# Returns all params to send to Solr remove all blank values.
#
def params
{
q: Solr::WhereNode.new(self.where),
rows: @per_page,
start: start,
sort: @order,
fq: Solr::WhereNode.new(@filter)
}.delete_if { |key, value| value.blank? }
end
# Create this alias just to be ActiveRecord like.
#
# @example
#
# Solr::Relation.new(where: 'name_pt:Saraiva').explain
#
alias :explain :params
end
end
class WhereNode < String
# @param node can be a String or a Hash
#
def initialize(node)
@node = node
super(build.to_s)
end
# Build 'q' param to send to Solr.
#
# @example
#
# # With String
# Solr::WhereNode.new('name_pt:"Saraiva"')
# # => 'name_pt:"Saraiva"'
#
# # With Hash
# Solr::WhereNode.new(name_pt: "Saraiva")
# # => 'name_pt:"Saraiva"'
#
# # With Hash with many fields
# Solr::WhereNode.new(name_pt: 'Saraiva', status: true)
# # => '(name_pt:"Saraiva") AND (status:true)'
#
# # With value as Array
# Solr::WhereNode.new(id: [1, 2, 3, 4])
# # => 'id:(1 OR 2 OR 3 OR 4)'
#
def build
return @node if @node.is_a?(String) or @node.blank?
@nodes = @node.collect.each { |key, value| build_criteria(key, value) }
if @nodes.size > 1
@nodes.map! { |node| "(#{node})" }.join(' AND ')
else
@nodes.join()
end
end
def build_criteria(key, value)
formatted_value = if value.is_a?(Array)
"(#{Array(value).join(' OR ')})"
else
build_value(value)
end
"#{key}:#{formatted_value}"
end
def build_value(value)
value == '*' ? value : %{"#{value}"}
end
end
class Base
include Virtus
extend ActiveModel::Naming
extend ActiveModel::Translation
def category_path(categories)
Array(categories.find { |category| category.id.to_i == category_id.to_i }.try(:parent_path))
end
class << self
attr_accessor :resource_name
def connection
RSolr.connect(
url: "#{Settings.solr.url}/#{resource_name}",
read_timeout: Settings.solr.read_timeout,
open_timeout: Settings.solr.open_timeout,
retry_503: Settings.solr.retry_503,
retry_after_limit: Settings.solr.retry_after_limit
)
end
# @api public
#
# Set the resource name that will be search in Solr Request.
#
# @example
#
# class MyClass < Solr::Base
# # Will search by solr_url/my_class
# resource :my_class
# end
#
def resource(name)
self.resource_name = name.to_s
end
# @api public
#
# Make the where criteria passing what will have inside the 'q' Solr param.
#
# @returns <Solr::Relation>
#
# @example
#
# Base.where("name_pt:Saraiva").order("name_pt ASC").limit(100).page(1)
#
def where(criteria)
Solr::Relation.new(connection: connection, relation_class: self, where: criteria)
end
# @api public
#
def find(id)
where(id: id).first
end
# @api public
# Make the basic search passing an object that responds to: #valid_for_basic_search?, #basic_query, #per_page and #page.
#
# @returns [Solr::Relation]
#
def basic_search(offer_search)
# return [] unless offer_search.valid_for_basic_search?
#
# search(offer_search)
end
# @api public
# Make the basic search passing an object that responds to: #basic_query, #per_page and #page.
#
# @returns [Solr::Relation]
#
def search(search)
# order_query = "#{fields[offer_search.order_field_name.to_sym]} #{offer_search.order_type}"
# where(offer_search.basic_query).per_page(offer_search.per_page).page(offer_search.page).order(order_query).filter(offer_search.filter)
end
end
def ==(other)
id == other.id
end
end
class ResponseHandler
attr_reader :resource_class
def initialize(resource_class, options={})
@resource_class = resource_class
@response = options.fetch(:response)
end
def docs
response['docs'].to_a
end
def total
response['numFound'].to_i
end
def records
docs.collect do |document|
resource_class.new.tap do |new_object|
attributes = new_object.attributes
attributes.each { |key, value| attributes[key] = document[key.to_s] }
new_object.attributes = attributes
end
end
end
def facet_fields
FacetFields.new(@response['facet_counts']['facet_fields'])
end
private
def response
@response['response'] || {}
end
end
class FacetFields < Hash
def initialize(fields={})
@fields = fields
assign_facet_field
super
end
def assign_facet_field
@fields.each do |facet_field, facet_values|
self[facet_field] = facet_values.each_slice(2).collect do |name, count|
FacetField.new(name: name, count: count, field: facet_field)
end
end
end
end
class FacetField
include ActiveModel::Model
attr_accessor :name, :count, :field
def ==(other_facet_field)
name == other_facet_field.name && count == other_facet_field.count && field == other_facet_field.field
end
end
module Solr
class FacetResponseHandler
attr_reader :field_name
def initialize(collection_object, options={})
@collection_object = collection_object
@response = options.fetch(:response)
@field_name = options[:field_name] || 'name_so'
end
def records
facet_fields[@field_name].each_slice(2).collect do |new_field_name, count|
@collection_object.new(@field_name => new_field_name)
end
end
def facet_fields
@response['facet_counts']['facet_fields']
end
end
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment