lib/services/search.rb in Active-0.0.14 vs lib/services/search.rb in Active-0.0.17
- old
+ new
@@ -2,10 +2,11 @@
require 'json'
require 'cgi'
require 'rubygems'
require 'mysql'
require 'active_record'
+require 'digest/md5'
module Active
module Services
# we should remove this class and just replace with symbols
@@ -28,24 +29,24 @@
end
end
class Search
attr_accessor :api_key, :start_date, :end_date, :location, :channels, :keywords, :search, :radius, :limit, :sort, :page, :offset, :latitude, :longitude,
- :view, :facet, :sort, :num_results, :asset_ids, :dma
+ :view, :facet, :sort, :num_results, :asset_ids, :dma, :city, :state, :country, :bounding_box
attr_reader :results, :endIndex, :pageSize, :searchTime, :numberOfResults, :end_point, :meta
SEARCH_URL = "http://search.active.com"
DEFAULT_TIMEOUT = 60
def initialize(data={})
self.api_key = data[:api_key] || ""
- self.location = data[:location] || ""
+ self.location = data[:location]
self.zips = data[:zips] || []
self.channels = data[:channels] || []
self.keywords = data[:keywords] || []
- self.radius = data[:radius] || "50"
+ self.radius = data[:radius] || nil
self.limit = data[:limit] || "10"
self.sort = data[:sort] || Sort.DATE_ASC
self.page = data[:page] || "1"
self.offset = data[:offset] || "0"
self.view = data[:view] || "json"
@@ -57,10 +58,14 @@
self.asset_ids = data[:asset_ids] || []
self.asset_id = data[:asset_id] || ""
self.latitude = data[:latitude]
self.longitude = data[:longitude]
self.dma = data[:dma]
+ self.city = data[:city]
+ self.state = data[:state]
+ self.country = data[:country]
+ self.bounding_box = data[:bounding_box]
end
# Example
# Search.search( {:zips => "92121, 92078, 92114"} )
# or
@@ -76,10 +81,11 @@
def zips
@zips
end
def location=(value)
+ return if value.nil?
@location = CGI.escape(value)
end
def keywords=(value)
if value.class == String
@@ -98,18 +104,29 @@
end
def asset_ids=(value)
@asset_ids = value
end
+
+ def bounding_box=(value)
+ return if value==nil
+ value = HashWithIndifferentAccess.new(value)
+ if value.has_key?("sw") && value.has_key?("ne")
+ @bounding_box=value
+ else
+ raise "bounding_box must be hash with keys sw and ne"
+ end
+ end
def asset_id=(value)
return if value.empty?
@asset_ids<<value
end
def end_point
- meta_data = ""
+ meta_data = ""
+ loc_str = ""
# CHANNELS
channel_keys = []
@channels.each do |c|
c.to_sym
if Categories.CHANNELS.include?(c)
@@ -125,20 +142,31 @@
temp_ids << "meta:" + CGI.escape("assetId=#{id.gsub("-","%2d")}")
end
meta_data += temp_ids.join("+OR+")
end
# LOCATION
- # 1 Look for zip codes
- # 2 Look for lat lng
- # 3 Look for a formatted string "San Diego, CA, US"
- # 4 Look for a dma
- if not @zips.empty?
+ # 1 Look for :city, :state, :country
+ # 2 Look for zip codes
+ # 3 Look for lat lng
+ # 4 Look for a formatted string "San Diego, CA, US"
+ # 5 Look for a dma
+ if @city or @state or @country
+ if @city
+ meta_data += "+AND+" unless meta_data == ""
+ meta_data += "meta:city=#{Search.double_encode_channel(@city)}"
+ end
+ if @state
+ meta_data += "+AND+" unless meta_data == ""
+ meta_data += "meta:state=#{Search.double_encode_channel(@state)}"
+ end
+ elsif !@zips.empty?
+ # if not @zips.empty?
loc_str = @zips.join(",")
elsif @latitude and @longitude
loc_str = "#{@latitude};#{@longitude}"
elsif @dma
- loc_str = ""
+
meta_data += "+AND+" unless meta_data == ""
meta_data += "meta:dma=#{Search.double_encode_channel(@dma)}"
else
loc_str = @location
end
@@ -152,21 +180,55 @@
if @end_date.class == Date
@end_date = URI.escape(@end_date.strftime("%m/%d/%Y")).gsub(/\//,"%2F")
end
meta_data += "meta:startDate:daterange:#{@start_date}..#{@end_date}"
- url = "#{SEARCH_URL}/search?api_key=#{@api_key}&num=#{@num_results}&page=#{@page}&l=#{loc_str}&f=#{@facet}&v=#{@view}&r=#{@radius}&s=#{@sort}&k=#{@keywords.join("+")}&m=#{meta_data}"
+ # BOUNDING BOX
+ if @bounding_box!=nil
+ #The values in the GSA metadata are shifted to prevent negative values. This was done b/c lat/long
+ # are searched as a number range and the GSA doesn't allow negative values in number ranges.
+ # We shift latitude values by 90 and longitude values by 180.
+
+ if @bounding_box[:sw].class==String
+ #String :bounding_box => { :sw => "37.695141,-123.013657", :ne => "37.832371,-122.356979"}
+ latitude1 = @bounding_box[:sw].split(",").first.to_f+90
+ latitude2 = @bounding_box[:ne].split(",").first.to_f+90
+ longitude1 = @bounding_box[:sw].split(",").last.to_f+180
+ longitude2 = @bounding_box[:ne].split(",").last.to_f+180
+ else
+ #hash query[:bounding_box] = { :sw => [123, 10], :ne => [222,10] }
+ latitude1 = @bounding_box[:sw].first.to_f+90
+ latitude2 = @bounding_box[:ne].first.to_f+90
+ longitude1 = @bounding_box[:sw].last.to_f+180
+ longitude2 = @bounding_box[:ne].last.to_f+180
+ end
+ meta_data += "+AND+" unless meta_data == ""
+ meta_data += "meta:latitudeShifted:#{latitude1}..#{latitude2}+AND+meta:longitudeShifted:#{longitude1}..#{longitude2}"
+ end
+
+ # url = "#{SEARCH_URL}/search?api_key=#{@api_key}&num=#{@num_results}&page=#{@page}&l=#{loc_str}&f=#{@facet}&v=#{@view}&r=#{@radius}&s=#{@sort}&k=#{@keywords.join("+")}&m=#{meta_data}"
+ urla = ["#{SEARCH_URL}/search?api_key=#{@api_key}"]
+ urla << "num=#{@num_results}" if @num_results
+ urla << "page=#{@page}" if @page
+ urla << "l=#{loc_str}" unless loc_str.nil? or loc_str.empty?
+ urla << "f=#{@facet}" if @facet
+ urla << "v=#{@view}" if @view
+ urla << "r=#{@radius}" if @radius
+ urla << "s=#{@sort}" if @sort
+ urla << "k=#{@keywords.join("+")}" if @keywords and !@keywords.empty?
+ urla << "m=#{meta_data}" if meta_data
+
+ return urla.join("&")
end
def search
searchurl = URI.parse(end_point)
req = Net::HTTP::Get.new(searchurl.path)
http = Net::HTTP.new(searchurl.host, searchurl.port)
http.read_timeout = DEFAULT_TIMEOUT
- puts "#{searchurl.path}?#{searchurl.query}"
-
+ puts "Active Search [GET] #{"#{searchurl.path}?#{searchurl.query}"}"
res = http.start { |http|
http.get("#{searchurl.path}?#{searchurl.query}")
}
if (200..307).include?(res.code.to_i)
@@ -174,11 +236,14 @@
parsed_json = JSON.parse(res.body)
@endIndex = parsed_json["endIndex"]
@pageSize = parsed_json["pageSize"]
@searchTime = parsed_json["searchTime"]
@numberOfResults = parsed_json["numberOfResults"]
- @results = parsed_json['_results'].collect { |a| Activity.new(a) }
+ @results = parsed_json['_results'].collect { |a| Activity.new(GSA.new(a)) }
+
+ Active.CACHE.set( Digest::MD5.hexdigest(end_point), self) if Active.CACHE
+
rescue JSON::ParserError => e
raise RuntimeError, "JSON::ParserError json=#{res.body}"
@endIndex = 0
@pageSize = 0
@searchTime = 0
@@ -221,14 +286,33 @@
#
#
# http://developer.active.com/docs/Activecom_Search_API_Reference
# returns an array of results and query info
def self.search(data=nil)
- search = Search.new(data)
- search.search
- return search
+ search = Search.new(data)
+ search_hash = Digest::MD5.hexdigest(search.end_point)
+ puts "search_hash #{search_hash}"
+ cache = Search.return_cached(search_hash)
+ if cache != nil
+ return cache
+ else
+ search.search
+ return search
+ end
end
+
+ def self.return_cached key
+ if Active.CACHE
+ cached_version = Active.CACHE.get(key)
+ if cached_version
+ puts "Active Search [CACHE] #{key}"
+ return cached_version
+ end
+ end
+ nil
+ end
+
def []
@results
end
@@ -237,10 +321,9 @@
str = URI.escape(str, Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))
str = URI.escape(str, Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))
str.gsub!(/\-/,"%252D")
str
end
-
end
# TODO move to a reflection service
class Categories
def self.CHANNELS
\ No newline at end of file