eshopworks-rboss 0.1.5 → 0.1.6

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt ADDED
@@ -0,0 +1,25 @@
1
+ === Version 0.1.6
2
+ * Implement the collection api will_paginate uses to display page links in the Boss::ResultCollection (Jared Pace)
3
+
4
+ === Version 0.1.5
5
+ * Use pure ruby json gem
6
+
7
+ === Version 0.1.4
8
+ * Parse yahoo error reports and return message
9
+ * Encapsulate spell/spelling difference into result factory
10
+ * Removed 'object' format in favour of defaulting to object if format is not set
11
+ * Validate app_id
12
+
13
+ === Version 0.1.3
14
+ * Fix for duplicate information being storied in the ResultCollection
15
+
16
+ === Version 0.1.2
17
+ * Maintain search result data in ResultCollection
18
+
19
+ === Version 0.1.1
20
+ * Fixed exact search error
21
+ * search method takes the same arguments as other search methods
22
+ * Encodes parameters correctly
23
+
24
+ === Version 0.1.0
25
+ * Brand spanking new release.
data/License.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008 eShopworks
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Manifest.txt ADDED
@@ -0,0 +1,37 @@
1
+ History.txt
2
+ License.txt
3
+ Manifest.txt
4
+ README.txt
5
+ Rakefile
6
+ TODO.txt
7
+ config/hoe.rb
8
+ config/requirements.rb
9
+ features/steps/web_search_steps.rb
10
+ features/web_search.feature
11
+ gem_tasks/cucumber.rake
12
+ gem_tasks/deployment.rake
13
+ gem_tasks/environment.rake
14
+ gem_tasks/fix_cr_lf.rake
15
+ gem_tasks/gemspec.rake
16
+ gem_tasks/rspec.rake
17
+ gem_tasks/verify_rcov.rake
18
+ gem_tasks/website.rake
19
+ lib/boss.rb
20
+ lib/boss/api.rb
21
+ lib/boss/config.rb
22
+ lib/boss/result.rb
23
+ lib/boss/result/base.rb
24
+ lib/boss/result/image.rb
25
+ lib/boss/result/news.rb
26
+ lib/boss/result/spell.rb
27
+ lib/boss/result/web.rb
28
+ lib/boss/result_collection.rb
29
+ lib/boss/result_factory.rb
30
+ lib/boss/version.rb
31
+ rboss.gemspec
32
+ spec/boss/api_spec.rb
33
+ spec/boss/config_spec.rb
34
+ spec/boss/result_collection_spec.rb
35
+ spec/boss/result_factory_spec.rb
36
+ spec/spec.opts
37
+ spec/spec_helper.rb
data/README.txt ADDED
@@ -0,0 +1,42 @@
1
+ = Rboss
2
+
3
+ * http://github.com/eshopworks/rboss-gem/
4
+
5
+ == DESCRIPTION:
6
+
7
+ A handy gem to make using the Yahoo Boss API nice and easy in ruby.
8
+ Documentation: http://github.com/eshopworks/rboss-gem/wikis/home
9
+
10
+ == REQUIREMENTS:
11
+
12
+ * json_pure: http://json.rubyforge.org/
13
+
14
+ == INSTALL:
15
+
16
+ * gem sources --add http://gems.github.com/
17
+ * gem install eshopworks-rboss
18
+
19
+ == LICENSE:
20
+
21
+ (The MIT License)
22
+
23
+ Copyright (c) 2008 eShopworks
24
+
25
+ Permission is hereby granted, free of charge, to any person obtaining
26
+ a copy of this software and associated documentation files (the
27
+ 'Software'), to deal in the Software without restriction, including
28
+ without limitation the rights to use, copy, modify, merge, publish,
29
+ distribute, sublicense, and/or sell copies of the Software, and to
30
+ permit persons to whom the Software is furnished to do so, subject to
31
+ the following conditions:
32
+
33
+ The above copyright notice and this permission notice shall be
34
+ included in all copies or substantial portions of the Software.
35
+
36
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
37
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
38
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
39
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
40
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
41
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
42
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,4 @@
1
+ require 'config/requirements'
2
+ require 'config/hoe' # setup Hoe + all gem configuration
3
+
4
+ Dir['gem_tasks/**/*.rake'].each { |rake| load rake }
data/TODO.txt ADDED
@@ -0,0 +1,4 @@
1
+ == FEATURES/PROBLEMS:
2
+
3
+ * Search all
4
+ * Integrate with Tagging service
data/config/hoe.rb ADDED
@@ -0,0 +1,69 @@
1
+ require 'boss'
2
+ require 'boss/version'
3
+
4
+ AUTHOR = 'Joseph Wilk' # can also be an array of Authors
5
+ EMAIL = "joe@eshopworks.co.uk"
6
+ DESCRIPTION = "Api wrapping Yahoo Boss search"
7
+ GEM_NAME = 'rboss' # what ppl will type to install your gem
8
+ HOMEPATH = "http://github.com/eshopworks/rboss-gem"
9
+ RUBYFORGE_PROJECT = nil
10
+
11
+ @config_file = "~/.rubyforge/user-config.yml"
12
+ @config = nil
13
+ RUBYFORGE_USERNAME = "eshopworks"
14
+ def rubyforge_username
15
+ unless @config
16
+ begin
17
+ @config = YAML.load(File.read(File.expand_path(@config_file)))
18
+ rescue
19
+ puts <<-EOS
20
+ ERROR: No rubyforge config file found: #{@config_file}
21
+ Run 'rubyforge setup' to prepare your env for access to Rubyforge
22
+ - See http://newgem.rubyforge.org/rubyforge.html for more details
23
+ EOS
24
+ exit
25
+ end
26
+ end
27
+ RUBYFORGE_USERNAME.replace @config["username"]
28
+ end
29
+
30
+
31
+ REV = nil
32
+ # UNCOMMENT IF REQUIRED:
33
+ # REV = YAML.load(`svn info`)['Revision']
34
+ VERS = Boss::VERSION::STRING + (REV ? ".#{REV}" : "")
35
+ RDOC_OPTS = ['--quiet', '--title', 'RBoss documentation',
36
+ "--opname", "index.html",
37
+ "--line-numbers",
38
+ "--main", "README.txt",
39
+ "--inline-source"]
40
+
41
+ class Hoe
42
+ def extra_deps
43
+ @extra_deps.reject! { |x| Array(x).first == 'hoe' }
44
+ @extra_deps
45
+ end
46
+ end
47
+
48
+ # Generate all the Rake tasks
49
+ # Run 'rake -T' to see list of generated tasks (from gem root directory)
50
+ $hoe = Hoe.new(GEM_NAME, VERS) do |p|
51
+ p.developer(AUTHOR, EMAIL)
52
+ p.description = DESCRIPTION
53
+ p.summary = DESCRIPTION
54
+ p.url = HOMEPATH
55
+ p.rubyforge_name = RUBYFORGE_PROJECT if RUBYFORGE_PROJECT
56
+ p.clean_globs |= ['**/.*.sw?', '*.gem', '.config', '**/.DS_Store', '**/*.class', '**/*.jar'] #An array of file patterns to delete on clean.
57
+
58
+ # == Optional
59
+ p.changes = p.paragraphs_of("History.txt", 0..1).join("\n\n")
60
+ #p.extra_deps = [] # An array of rubygem dependencies [name, version], e.g. [ ['active_support', '>= 1.3.1'] ]
61
+ p.extra_deps = [ ['json_pure', '>= 1.1.3'], ['rspec', '>= 1.1.4'], ['diff-lcs', '>= 1.1.2'] ]
62
+
63
+ #p.spec_extras = {} # A hash of extra values to set in the gemspec.
64
+ end
65
+
66
+ CHANGES = $hoe.paragraphs_of('History.txt', 0..1).join("\\n\\n")
67
+ PATH = (RUBYFORGE_PROJECT == GEM_NAME) ? RUBYFORGE_PROJECT : "#{RUBYFORGE_PROJECT}/#{GEM_NAME}"
68
+ $hoe.remote_rdoc_dir = File.join(PATH.gsub(/^#{RUBYFORGE_PROJECT}\/?/,''), 'rdoc')
69
+ $hoe.rsync_args = '-av --delete --ignore-errors'
@@ -0,0 +1,15 @@
1
+ require 'fileutils'
2
+ include FileUtils
3
+
4
+ require 'rubygems'
5
+ %w[rake hoe].each do |req_gem|
6
+ begin
7
+ require req_gem
8
+ rescue LoadError
9
+ puts "This Rakefile requires the '#{req_gem}' RubyGem."
10
+ puts "Installation: gem install #{req_gem} -y"
11
+ exit
12
+ end
13
+ end
14
+
15
+ $:.unshift(File.join(File.dirname(__FILE__), %w[.. lib]))
@@ -0,0 +1,38 @@
1
+ require 'spec'
2
+ $:.unshift(File.dirname(__FILE__) + '/../../lib')
3
+ require 'boss'
4
+
5
+ Before do
6
+ end
7
+
8
+ After do
9
+ end
10
+
11
+ Given "a valid API key" do
12
+ @api = Boss::Api.new("put-your-api-key-here")
13
+ end
14
+
15
+ When /I do a '(.+)' search for '(.+)'/ do |search, term|
16
+
17
+ case search
18
+ when 'web'
19
+ @results = @api.search_web(term)
20
+ when 'news'
21
+ @results = @api.search_news(term)
22
+ when 'images'
23
+ @results = @api.search_images(term)
24
+ when 'spell'
25
+ @results = @api.search_spelling(term)
26
+ else
27
+ raise Exception.new "invalid search: #{search}"
28
+ end
29
+
30
+ end
31
+
32
+ Then /I will receive search results/ do
33
+ @results.results.nil?.should == false
34
+ end
35
+
36
+ Then /I will be able to see the total hits/ do
37
+ @results.totalhits.to_i.should > 0
38
+ end
@@ -0,0 +1,17 @@
1
+ Feature: Web Search
2
+ In order to get search results
3
+ As a API user
4
+ I want to query yahoo boss
5
+
6
+ Scenario: Search web
7
+ Given a valid API key
8
+ When I do a 'web' search for 'monkeys'
9
+ Then I will receive search results
10
+ And I will be able to see the total hits
11
+
12
+ | type | term |
13
+ | web | monkey |
14
+ | images | monkey |
15
+ | news | monkey |
16
+ | spell | girafe |
17
+
@@ -0,0 +1,6 @@
1
+ require 'cucumber/rake/task'
2
+
3
+ Cucumber::Rake::Task.new(:features) do |t|
4
+ t.cucumber_opts = "--format pretty"
5
+ t.step_pattern = "features/steps/"
6
+ end
@@ -0,0 +1,34 @@
1
+ desc 'Release the website and new gem version'
2
+ task :deploy => [:check_version, :website, :release] do
3
+ puts "Remember to create SVN tag:"
4
+ puts "svn copy svn+ssh://#{rubyforge_username}@rubyforge.org/var/svn/#{PATH}/trunk " +
5
+ "svn+ssh://#{rubyforge_username}@rubyforge.org/var/svn/#{PATH}/tags/REL-#{VERS} "
6
+ puts "Suggested comment:"
7
+ puts "Tagging release #{CHANGES}"
8
+ end
9
+
10
+ desc 'Runs tasks website_generate and install_gem as a local deployment of the gem'
11
+ task :local_deploy => [:website_generate, :install_gem]
12
+
13
+ task :check_version do
14
+ unless ENV['VERSION']
15
+ puts 'Must pass a VERSION=x.y.z release version'
16
+ exit
17
+ end
18
+ unless ENV['VERSION'] == VERS
19
+ puts "Please update your version.rb to match the release version, currently #{VERS}"
20
+ exit
21
+ end
22
+ end
23
+
24
+ desc 'Install the package as a gem, without generating documentation(ri/rdoc)'
25
+ task :install_gem_no_doc => [:clean, :package] do
26
+ sh "#{'sudo ' unless Hoe::WINDOZE }gem install pkg/*.gem --no-rdoc --no-ri"
27
+ end
28
+
29
+ namespace :manifest do
30
+ desc 'Recreate Manifest.txt to include ALL files'
31
+ task :refresh do
32
+ `rake check_manifest | patch -p0 > Manifest.txt`
33
+ end
34
+ end
@@ -0,0 +1,7 @@
1
+ task :ruby_env do
2
+ RUBY_APP = if RUBY_PLATFORM =~ /java/
3
+ "jruby"
4
+ else
5
+ "ruby"
6
+ end unless defined? RUBY_APP
7
+ end
@@ -0,0 +1,10 @@
1
+ desc 'Make all files use UNIX (\n) line endings'
2
+ task :fix_cr_lf do
3
+ files = FileList['**/*']
4
+ files.each do |f|
5
+ next if File.directory?(f)
6
+ s = IO.read(f)
7
+ s.gsub!(/\r?\n/, "\n")
8
+ File.open(f, "w") { |io| io.write(s) }
9
+ end
10
+ end
@@ -0,0 +1,6 @@
1
+ namespace :gemspec do
2
+ desc 'Refresh rboss-gem.gemspec to include ALL files'
3
+ task :refresh => 'manifest:refresh' do
4
+ File.open('rboss.gemspec', 'w') {|io| io.write($hoe.spec.to_ruby)}
5
+ end
6
+ end
@@ -0,0 +1,27 @@
1
+ begin
2
+ require 'spec'
3
+ rescue LoadError
4
+ require 'rubygems'
5
+ require 'spec'
6
+ end
7
+ begin
8
+ require 'spec/rake/spectask'
9
+ rescue LoadError
10
+ puts <<-EOS
11
+ To use rspec for testing you must install rspec gem:
12
+ gem install rspec
13
+ EOS
14
+ exit(0)
15
+ end
16
+
17
+ desc "Run the specs under spec/models"
18
+ Spec::Rake::SpecTask.new do |t|
19
+ t.spec_opts = ['--options', "spec/spec.opts"]
20
+ t.spec_files = FileList['spec/**/*_spec.rb']
21
+
22
+ unless ENV['NO_RCOV']
23
+ t.rcov = true
24
+ t.rcov_dir = 'coverage'
25
+ t.rcov_opts = ['--exclude', 'spec\/boss,bin\/spec,examples,\/var\/lib\/gems,\/Library\/Ruby,\.autotest']
26
+ end
27
+ end
@@ -0,0 +1,7 @@
1
+ require 'rake'
2
+ require 'spec/rake/verify_rcov'
3
+
4
+ RCov::VerifyTask.new(:verify_rcov => :spec) do |t|
5
+ t.threshold = 99.7 # Make sure you have rcov 0.9 or higher!
6
+ t.index_html = 'coverage/index.html'
7
+ end
@@ -0,0 +1,17 @@
1
+ desc 'Generate website files'
2
+ task :website_generate => :ruby_env do
3
+ (Dir['website/**/*.txt'] - Dir['website/version*.txt']).each do |txt|
4
+ sh %{ #{RUBY_APP} script/txt2html #{txt} > #{txt.gsub(/txt$/,'html')} }
5
+ end
6
+ end
7
+
8
+ desc 'Upload website files to rubyforge'
9
+ task :website_upload do
10
+ host = "#{rubyforge_username}@rubyforge.org"
11
+ remote_dir = "/var/www/gforge-projects/#{PATH}/"
12
+ local_dir = 'website'
13
+ sh %{rsync -aCv #{local_dir}/ #{host}:#{remote_dir}}
14
+ end
15
+
16
+ desc 'Generate and upload website files'
17
+ task :website => [:website_generate, :website_upload, :publish_docs]
data/lib/boss.rb ADDED
@@ -0,0 +1,23 @@
1
+ $:.unshift(File.dirname(__FILE__)) unless
2
+ $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
3
+
4
+ require 'boss/api'
5
+ require 'boss/config'
6
+ require 'boss/result'
7
+ require 'boss/result_collection'
8
+ require 'boss/result_factory'
9
+ require 'boss/version'
10
+
11
+ module Boss
12
+ YAHOO_VERSION = 1
13
+
14
+ module SearchService
15
+ %w[web images news spelling].each { |e| const_set(e.upcase, e) }
16
+ end
17
+
18
+ FORMATS = %w[xml json]
19
+
20
+ class BossError < StandardError; end
21
+ class InvalidFormat < StandardError; end
22
+ class InvalidConfig < StandardError; end
23
+ end
data/lib/boss/api.rb ADDED
@@ -0,0 +1,97 @@
1
+ require 'net/http'
2
+ require 'rexml/document'
3
+ require 'uri'
4
+
5
+ require 'cgi'
6
+
7
+ module Boss
8
+
9
+ class Api
10
+
11
+ attr_accessor :endpoint
12
+
13
+ def initialize(app_id)
14
+ @app_id = app_id
15
+ @endpoint = 'http://boss.yahooapis.com/ysearch/'
16
+ end
17
+
18
+ def search(term, *conditions, &block)
19
+ search_boss(term, SearchService::WEB, *conditions, &block)
20
+ end
21
+
22
+ def search_images(term, *conditions, &block)
23
+ search_boss(term, SearchService::IMAGES, *conditions, &block)
24
+ end
25
+
26
+ def search_news(term, *conditions, &block)
27
+ search_boss(term, SearchService::NEWS, *conditions, &block)
28
+ end
29
+
30
+ def search_web(term, *conditions, &block)
31
+ search_boss(term, SearchService::WEB, *conditions, &block)
32
+ end
33
+
34
+ def search_spelling(term, *conditions, &block)
35
+ search_boss(term, SearchService::SPELLING, *conditions, &block)
36
+ end
37
+
38
+ private
39
+ def search_boss(terms, search_type=SearchService::WEB, config = {})
40
+ config = config.empty? ? Config.new : Config.new(config)
41
+ yield config if block_given?
42
+
43
+ raise InvalidFormat, "'#{config.format}' is not a valid format. Valid formats are: #{FORMATS.join(',')}" unless FORMATS.include?(config.format) || config.format?
44
+ raise InvalidConfig, "count must be > 0" unless config.count>0
45
+ raise InvalidConfig, "App ID cannot be empty!" if @app_id.empty?
46
+
47
+ request = URI.parse(build_request_url(terms, search_type, config))
48
+ response = Net::HTTP.get_response(request)
49
+
50
+ case response.code
51
+ when "200"
52
+ data = response.body
53
+
54
+ if config.format?
55
+ search_results = ResultFactory.build(data)
56
+
57
+ # set requested page count size
58
+ # Used in math to determine total pages and current page
59
+ search_results.set_instance_variable('page_count', config.count) if search_results.kind_of?(Boss::ResultCollection)
60
+ else
61
+ search_results = data
62
+ end
63
+ else
64
+ raise BossError, parse_error(response)
65
+ end
66
+
67
+ search_results
68
+ end
69
+
70
+ private
71
+ def parse_error(data)
72
+ doc = REXML::Document.new(data.body)
73
+ # message = doc.elements['Error/Message'].text
74
+ message = REXML::XPath.first( doc, "//Message" )
75
+ if message
76
+ message.text
77
+ else
78
+ "Error contacting Yahoo Boss web-service"
79
+ end
80
+ end
81
+
82
+ private
83
+ def build_request_url(terms, search_type, config)
84
+ #We could use URI.encode but it leaves things like ? unencoded which fails search.
85
+ encoded_terms = CGI.escape(terms)
86
+ # puts "#{@endpoint}#{search_type}/#{boss_version}/#{encoded_terms}?appid=#{@app_id}#{config.to_url}"
87
+ "#{@endpoint}#{search_type}/#{boss_version}/#{encoded_terms}?appid=#{@app_id}#{config.to_url}"
88
+ end
89
+
90
+ private
91
+ def boss_version
92
+ "v#{Boss::YAHOO_VERSION}"
93
+ end
94
+
95
+ end
96
+
97
+ end
@@ -0,0 +1,22 @@
1
+ require 'ostruct'
2
+
3
+ module Boss
4
+
5
+ class Config < OpenStruct
6
+
7
+ def initialize(hash={})
8
+ #Setup defaults
9
+ super({:count => 10, :lang => "en"}.merge(hash))
10
+ end
11
+
12
+ def to_url
13
+ self.marshal_dump.inject("") {|accum, key| encoded_value=CGI.escape(key[1].to_s); accum+="&#{key[0]}=#{encoded_value}" }
14
+ end
15
+
16
+ def format?
17
+ self.format.nil?
18
+ end
19
+
20
+ end
21
+
22
+ end
@@ -0,0 +1 @@
1
+ %w{base web image news spell}.each{|f| require "boss/result/#{f}"}
@@ -0,0 +1,15 @@
1
+ require 'ostruct'
2
+
3
+ module Boss
4
+ module Result
5
+ class Base < OpenStruct
6
+
7
+ def initialize(data={})
8
+ super
9
+ end
10
+
11
+ end
12
+
13
+ end
14
+
15
+ end
@@ -0,0 +1,8 @@
1
+ module Boss
2
+ module Result
3
+ class Image < Base
4
+ end
5
+
6
+ end
7
+
8
+ end
@@ -0,0 +1,8 @@
1
+ module Boss
2
+ module Result
3
+ class News < Base
4
+ end
5
+
6
+ end
7
+
8
+ end
@@ -0,0 +1,8 @@
1
+ module Boss
2
+ module Result
3
+ class Spell < Base
4
+ end
5
+
6
+ end
7
+
8
+ end
@@ -0,0 +1,8 @@
1
+ module Boss
2
+ module Result
3
+ class Web < Base
4
+ end
5
+
6
+ end
7
+
8
+ end
@@ -0,0 +1,55 @@
1
+ module Boss
2
+ class ResultCollection
3
+ include Enumerable
4
+
5
+ attr_reader :results
6
+
7
+ def initialize
8
+ @results=[]
9
+ end
10
+
11
+ def set_instance_variable(name, value)
12
+ instance_variable_set("@#{name}",value)
13
+ instance_eval("def #{name}\n @#{name}\n end")
14
+ end
15
+
16
+ def each
17
+ @results.each { |result| yield result }
18
+ end
19
+
20
+ def <<(element)
21
+ @results << element
22
+ end
23
+
24
+ def [](key)
25
+ @results[key]
26
+ end
27
+
28
+ # Implements neccessary api for the will_paginate view helper
29
+ # to work with result sets out of the box
30
+ def size
31
+ @results.size
32
+ end
33
+
34
+ def empty?
35
+ @results.empty?
36
+ end
37
+
38
+ def previous_page
39
+ self.current_page == 1 ? nil : self.current_page.to_i-1
40
+ end
41
+
42
+ def next_page
43
+ self.current_page == self.total_pages ? nil : self.current_page+1
44
+ end
45
+
46
+ def total_pages
47
+ (self.totalhits.to_f/self.page_count.to_f).ceil
48
+ end
49
+
50
+ def current_page
51
+ (self.start.to_i/self.page_count.to_i) + 1
52
+ end
53
+
54
+ end
55
+ end
@@ -0,0 +1,61 @@
1
+ require 'json'
2
+
3
+ module Boss
4
+
5
+ class ResultFactory
6
+
7
+ module SearchType
8
+ %w[web images news spell].each { |e| const_set(e.upcase, e) }
9
+ end
10
+
11
+ SEARCH_RESPONSE = 'ysearchresponse'
12
+ RESULT_SET = 'resultset'
13
+
14
+ class << self
15
+
16
+ def build(data)
17
+ json_hash = JSON.parse(data)
18
+
19
+ if json_hash.has_key? 'Error' or !json_hash.has_key? SEARCH_RESPONSE
20
+ raise BossError, "Results from webservice appear to be mangled."
21
+ end
22
+
23
+ result_collection = ResultCollection.new
24
+
25
+ json_hash[SEARCH_RESPONSE].each do |key,value|
26
+
27
+ if key.include? "#{RESULT_SET}_"
28
+
29
+ search_type = discover_search_type(key)
30
+
31
+ json_hash[SEARCH_RESPONSE][key].each do |result|
32
+
33
+ case search_type
34
+ when SearchType::WEB
35
+ result_collection << Result::Web.new(result)
36
+ when SearchType::IMAGES
37
+ result_collection << Result::Image.new(result)
38
+ when SearchType::NEWS
39
+ result_collection << Result::News.new(result)
40
+ when SearchType::SPELL
41
+ result_collection << Result::Spell.new(result)
42
+ end
43
+
44
+ end
45
+ else
46
+ result_collection.set_instance_variable(key, value)
47
+ end
48
+
49
+ end
50
+ result_collection
51
+ end
52
+
53
+ private
54
+ def discover_search_type(string)
55
+ /#{RESULT_SET}_(.*)/.match(string)[1]
56
+ end
57
+
58
+ end
59
+
60
+ end
61
+ end
@@ -0,0 +1,9 @@
1
+ module Boss #:nodoc:
2
+ class VERSION #:nodoc:
3
+ MAJOR = 0
4
+ MINOR = 1
5
+ TINY = 6
6
+
7
+ STRING = [MAJOR, MINOR, TINY].join('.')
8
+ end
9
+ end
data/rboss.gemspec ADDED
@@ -0,0 +1,41 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = %q{rboss}
3
+ s.version = "0.1.6"
4
+
5
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
6
+ s.authors = ["Joseph Wilk"]
7
+ s.date = %q{2008-10-17}
8
+ s.description = %q{Api wrapping Yahoo Boss search}
9
+ s.email = ["joe@eshopworks.co.uk"]
10
+ s.extra_rdoc_files = ["History.txt", "License.txt", "Manifest.txt", "README.txt", "TODO.txt"]
11
+ s.files = ["History.txt", "License.txt", "Manifest.txt", "README.txt", "Rakefile", "TODO.txt", "config/hoe.rb", "config/requirements.rb", "features/steps/web_search_steps.rb", "features/web_search.feature", "gem_tasks/cucumber.rake", "gem_tasks/deployment.rake", "gem_tasks/environment.rake", "gem_tasks/fix_cr_lf.rake", "gem_tasks/gemspec.rake", "gem_tasks/rspec.rake", "gem_tasks/verify_rcov.rake", "gem_tasks/website.rake", "lib/boss.rb", "lib/boss/api.rb", "lib/boss/config.rb", "lib/boss/result.rb", "lib/boss/result/base.rb", "lib/boss/result/image.rb", "lib/boss/result/news.rb", "lib/boss/result/spell.rb", "lib/boss/result/web.rb", "lib/boss/result_collection.rb", "lib/boss/result_factory.rb", "lib/boss/version.rb", "rboss.gemspec", "spec/boss/api_spec.rb", "spec/boss/config_spec.rb", "spec/boss/result_collection_spec.rb", "spec/boss/result_factory_spec.rb", "spec/spec.opts", "spec/spec_helper.rb"]
12
+ s.has_rdoc = true
13
+ s.homepage = %q{http://github.com/eshopworks/rboss-gem}
14
+ s.rdoc_options = ["--main", "README.txt"]
15
+ s.require_paths = ["lib"]
16
+ s.rubyforge_project = %q{rboss}
17
+ s.rubygems_version = %q{1.2.0}
18
+ s.summary = %q{Api wrapping Yahoo Boss search}
19
+
20
+ if s.respond_to? :specification_version then
21
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
22
+ s.specification_version = 2
23
+
24
+ if current_version >= 3 then
25
+ s.add_runtime_dependency(%q<json_pure>, [">= 1.1.3"])
26
+ s.add_runtime_dependency(%q<rspec>, [">= 1.1.4"])
27
+ s.add_runtime_dependency(%q<diff-lcs>, [">= 1.1.2"])
28
+ s.add_development_dependency(%q<hoe>, [">= 1.8.0"])
29
+ else
30
+ s.add_dependency(%q<json_pure>, [">= 1.1.3"])
31
+ s.add_dependency(%q<rspec>, [">= 1.1.4"])
32
+ s.add_dependency(%q<diff-lcs>, [">= 1.1.2"])
33
+ s.add_dependency(%q<hoe>, [">= 1.8.0"])
34
+ end
35
+ else
36
+ s.add_dependency(%q<json_pure>, [">= 1.1.3"])
37
+ s.add_dependency(%q<rspec>, [">= 1.1.4"])
38
+ s.add_dependency(%q<diff-lcs>, [">= 1.1.2"])
39
+ s.add_dependency(%q<hoe>, [">= 1.8.0"])
40
+ end
41
+ end
@@ -0,0 +1,187 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+ # require File.dirname(__FILE__) + '/../../boss/api.rb'
3
+
4
+ describe Boss::Api do
5
+
6
+ yahoo_error=<<-EOF
7
+ <Error xmlns="urn:yahoo:api"
8
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
9
+ xsi:noNamespaceSchemaLocation="http://api.yahoo.com/Api/V1/error.xsd">
10
+ The following errors were detected:
11
+ <Message>Service not found. Please see http://developer.yahoo.net for service locations</Message>
12
+ </Error>
13
+ EOF
14
+
15
+ #TODO: Mock HTTPSuccess
16
+ def mock_http_response(stubs={})
17
+ # mock('Net::HTTPSuccess',{:head => Net::HTTPSuccess.new('1.2', '200', 'OK'), :body => '{"ysearchresponse":{}}' })
18
+ mock('http_response', {:body => '{"ysearchresponse":{}}', :code => "200"}.merge(stubs))
19
+ end
20
+
21
+ def yahoo_json
22
+ '{"ysearchresponse":{}}'
23
+ end
24
+
25
+ before(:each) do
26
+ @api = Boss::Api.new( appid = 'test' )
27
+ @api.endpoint = 'http://www.example.com/'
28
+ end
29
+
30
+ describe "responding to spelling search" do
31
+
32
+ it "should make a spelling request to yahoo service" do
33
+ Net::HTTP.should_receive(:get_response).and_return{ mock_http_response }
34
+
35
+ @api.search_spelling("girafes")
36
+ end
37
+
38
+ it "should build the spelling objects" do
39
+ Net::HTTP.stub!(:get_response).and_return{ mock_http_response }
40
+ Boss::ResultFactory.should_receive(:build).with(yahoo_json)
41
+
42
+ @api.search_spelling("girafes")
43
+ end
44
+
45
+ end
46
+
47
+ describe "responding to news search" do
48
+ it "should make a news request to yahoo service" do
49
+ Net::HTTP.should_receive(:get_response).and_return{ mock_http_response }
50
+
51
+ @api.search_news("monkey")
52
+ end
53
+
54
+ it "should build the news objects" do
55
+ Net::HTTP.stub!(:get_response).and_return{ mock_http_response }
56
+ Boss::ResultFactory.should_receive(:build).with(yahoo_json)
57
+
58
+ @api.search_news("monkey")
59
+ end
60
+ end
61
+
62
+ describe "responding to image search" do
63
+ it "should make a image request to yahoo service" do
64
+ Net::HTTP.should_receive(:get_response).and_return{ mock_http_response }
65
+
66
+ @api.search_images("hippo")
67
+ end
68
+
69
+ it "should build the image objects" do
70
+ Net::HTTP.stub!(:get_response).and_return{ mock_http_response }
71
+ Boss::ResultFactory.should_receive(:build).with(yahoo_json)
72
+
73
+ @api.search_images("hippo")
74
+ end
75
+ end
76
+
77
+ describe "responding to web search" do
78
+
79
+ it "should make a web request to yahoo service" do
80
+ Net::HTTP.should_receive(:get_response).and_return{ mock_http_response }
81
+
82
+ @api.search_web("monkey")
83
+ end
84
+
85
+ it "should build the web objects" do
86
+ Net::HTTP.stub!(:get_response).and_return{ mock_http_response }
87
+ Boss::ResultFactory.should_receive(:build).with(yahoo_json)
88
+
89
+ @api.search_web("monkey")
90
+ end
91
+
92
+ end
93
+
94
+ describe "failed search" do
95
+
96
+ it "should raise error on failed search" do
97
+ Net::HTTP.stub!(:get_response).and_return{ mock_http_response :code => "404" }
98
+
99
+ lambda { @api.search_web("monkey") }.should raise_error(Boss::BossError)
100
+ end
101
+
102
+ it "should extract error from xml on failed search" do
103
+ Net::HTTP.stub!(:get_response).and_return{ mock_http_response :code => "404", :body => yahoo_error }
104
+
105
+ lambda { @api.search_web("monkey") }.should raise_error(Boss::BossError, 'Service not found. Please see http://developer.yahoo.net for service locations')
106
+ end
107
+
108
+ end
109
+
110
+ describe "configuring search" do
111
+
112
+ before(:each) do
113
+ Net::HTTP.stub!(:get_response).and_return{ mock_http_response }
114
+
115
+ @config = Boss::Config.new
116
+ end
117
+
118
+ it "should allow configuring through block" do
119
+ @config.should_receive(:count=).with(1)
120
+ Boss::Config.should_receive(:new).and_return(@config)
121
+
122
+ result = @api.search_web("monkeys") do |setup|
123
+ setup.count = 1
124
+ end
125
+ end
126
+
127
+ it "should allow configuring through hash" do
128
+ Boss::Config.should_receive(:new).with({:count => 1}).and_return(@config)
129
+
130
+ @api.search_web("monkeys", :count => 1)
131
+ end
132
+
133
+ end
134
+
135
+ describe "formats" do
136
+
137
+ before(:each) do
138
+ Net::HTTP.stub!(:get_response).and_return{ mock_http_response }
139
+ end
140
+
141
+ it "should not return any objects when format is 'xml'" do
142
+ Boss::ResultFactory.should_receive(:build).never
143
+ @api.search_web("monkeys", :format => 'xml', :count => 1)
144
+ end
145
+
146
+ it "should not return any objects when format is 'json'" do
147
+ Boss::ResultFactory.should_receive(:build).never
148
+ @api.search_web("monkeys", :format => 'json', :count => 1)
149
+ end
150
+
151
+ it "should raise an error invalid format" do
152
+ lambda { @api.search_web("monkeys", :format => 'grilled_cheese', :count => 1) }.should raise_error(Boss::InvalidFormat)
153
+ end
154
+
155
+ it "should raise an error on invalid count" do
156
+ lambda { @api.search_web("monkeys", :count => 0) }.should raise_error(Boss::InvalidConfig)
157
+ end
158
+
159
+ it "should raise an error on invalid app id" do
160
+ @api = Boss::Api.new( app_id = '' )
161
+
162
+ lambda { @api.search("monkeys", :count => 1) }.should raise_error(Boss::InvalidConfig)
163
+ end
164
+
165
+ end
166
+
167
+ describe "search should still work when get returns a successful but not code 200" do
168
+ it "should description" do
169
+ pending("fix for http://eshopworks.lighthouseapp.com/projects/15732/tickets/1")
170
+ Net::HTTP.stub!(:get_response).and_return{ mock_http_response :code => "206" }
171
+
172
+ lambda { @api.search_web("monkey") }.should_not raise_error(Boss::BossError)
173
+ end
174
+ end
175
+
176
+ describe "searching terms" do
177
+ it "should encode invalid characters" do
178
+ Net::HTTP.stub!(:get_response).and_return{ mock_http_response }
179
+ CGI.stub!(:escape)
180
+ CGI.should_receive(:escape).with('monkey?magic').and_return('monkey%3Fmagic')
181
+
182
+ @api.search_web("monkey?magic")
183
+ end
184
+
185
+ end
186
+
187
+ end
@@ -0,0 +1,31 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ describe Boss::Config do
4
+
5
+ it "should create with defaults" do
6
+ config = Boss::Config.new
7
+
8
+ config.respond_to?(:count).should be_true
9
+ config.respond_to?(:lang).should be_true
10
+ end
11
+
12
+ it "should create url from defaults" do
13
+ config = Boss::Config.new :count => 1, :lang => 'en', :format => "object"
14
+
15
+ config.to_url.should include("&count=1")
16
+ config.to_url.should include("&lang=en")
17
+ end
18
+
19
+ it "should add custom values to url" do
20
+ config = Boss::Config.new(:mizaru => 'cannot_see')
21
+
22
+ config.to_url.should include("&mizaru=cannot_see")
23
+ end
24
+
25
+ it "should encode invalid url characters" do
26
+ config = Boss::Config.new(:mizaru => 'dancing monkeys?')
27
+
28
+ config.to_url.should include("dancing+monkeys%3F")
29
+ end
30
+
31
+ end
@@ -0,0 +1,73 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ describe Boss::ResultCollection do
4
+
5
+ it "should dynamically set instance values given at creation" do
6
+ collection = Boss::ResultCollection.new
7
+ collection.set_instance_variable(:totalhits, "344")
8
+
9
+ collection.totalhits.should eql("344")
10
+ end
11
+
12
+ it "should allow iterating over result collection" do
13
+ collection = Boss::ResultCollection.new
14
+
15
+ collection << 1
16
+ collection << 2
17
+
18
+ collection.each do |value|
19
+ [1,2].member?(value).should be_true
20
+ end
21
+
22
+ end
23
+
24
+ end
25
+
26
+ describe Boss::ResultCollection, 'implementing the will_paginate collection api' do
27
+ before(:each) do
28
+ @collection = Boss::ResultCollection.new
29
+ @collection.set_instance_variable(:totalhits, "99")
30
+ @collection.set_instance_variable(:count, "10")
31
+ @collection.set_instance_variable(:page_count, "10")
32
+ end
33
+
34
+ it "should be able to return the collection size" do
35
+ @collection << 1
36
+ @collection << 2
37
+
38
+ @collection.size.should eql(2)
39
+ end
40
+
41
+ it "should be empty if no results have been added" do
42
+ @collection.should be_empty
43
+ end
44
+
45
+ it "should not have a previous page if it is on page 1" do
46
+ @collection.set_instance_variable(:start, "0")
47
+ @collection.previous_page.should be_nil
48
+ end
49
+
50
+ it "should return the correct previous page" do
51
+ @collection.set_instance_variable(:start, "10")
52
+ @collection.previous_page.should eql(1)
53
+ end
54
+
55
+ it "should return the correct current page" do
56
+ @collection.set_instance_variable(:start, "10")
57
+ @collection.current_page.should eql(2)
58
+ end
59
+
60
+ it "should return the correct next page" do
61
+ @collection.set_instance_variable(:start, "10")
62
+ @collection.next_page.should eql(3)
63
+ end
64
+
65
+ it "should not have a next page if we are on the last page" do
66
+ @collection.set_instance_variable(:start, "90")
67
+ @collection.next_page.should be_nil
68
+ end
69
+
70
+ it "should return the correct number of total pages" do
71
+ @collection.total_pages.should eql(10)
72
+ end
73
+ end
@@ -0,0 +1,57 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ describe Boss::ResultFactory do
4
+
5
+ news_json_result = '{"ysearchresponse":{"responsecode":"200","nextpage":"nextpage","totalhits":"344","count":"1","start":"0","resultset_news":[{"abstract":"abstract","clickurl":"clickurl","date":"2008\/08\/18","language":"en english","source":"source","sourceurl":"sourceurl","time":"09:17:29","title":"monkey_title","url":"url"}]}}'
6
+
7
+ image_json_result = '{"ysearchresponse":{"responsecode":"200","nextpage":"http:\/\/www.example.com","totalhits":"80","count":"1","start":"0","resultset_images":[{"abstract":"more monkeys","clickurl":"http:\/\/www.example.con\/click","date":"2001\/12\/19","filename":"monkeys.jpg","format":"jpeg","height":"600","mimetype":"image\/jpeg","refererclickurl":"http:\/\/www.example.com\/ref","refererurl":"http:\/\/www.monkeys.com","size":"60900","thumbnail_height":"116","thumbnail_url":"http:\/\/monkey.com\/image\/25\/m7\/3918546506","thumbnail_width":"155","title":"monkeys.jpg","url":"http:\/\/monkey\/monkeys.jpg","width":"800"}]}}'
8
+
9
+ web_json_result = '{"ysearchresponse":{"responsecode":"200","nextpage":"nextpage","totalhits":"344","count":"1","start":"0","resultset_web":[{"abstract":"abstract","clickurl":"clickurl","date":"2008\/08\/18","language":"en english","source":"source","sourceurl":"sourceurl","time":"09:17:29","title":"monkey_title","url":"url"}]}}'
10
+
11
+ spelling_json_result = '{"ysearchresponse":{"responsecode":"200","totalhits":"1","count":"1","start":"0","resultset_spell":[{"suggestion":"giraffes"}]}}'
12
+
13
+ error_json_result = '{"Error":"true"}'
14
+
15
+ it "should collect result objects in a result collection" do
16
+ result_collection = Boss::ResultCollection.new
17
+ result_collection.should_receive(:<<).once
18
+ Boss::ResultCollection.should_receive(:new).once.and_return(result_collection)
19
+
20
+ Boss::ResultFactory.build(news_json_result)
21
+ end
22
+
23
+ it "should create a new news object from json" do
24
+ Boss::Result::News.should_receive(:new).once
25
+
26
+ Boss::ResultFactory.build(news_json_result)
27
+ end
28
+
29
+ it "should correctly map fields with from json to news object" do
30
+ news_results = Boss::ResultFactory.build(news_json_result)
31
+
32
+ news_results[0].title.should == "monkey_title"
33
+ end
34
+
35
+ it "should build image objects from json" do
36
+ Boss::Result::Image.should_receive(:new).once
37
+
38
+ Boss::ResultFactory.build(image_json_result)
39
+ end
40
+
41
+ it "should build web object from json" do
42
+ Boss::Result::Web.should_receive(:new).once
43
+
44
+ Boss::ResultFactory.build(web_json_result)
45
+ end
46
+
47
+ it "should build spelling object from json" do
48
+ Boss::Result::Spell.should_receive(:new).once
49
+
50
+ Boss::ResultFactory.build(spelling_json_result)
51
+ end
52
+
53
+ it "should raise an error if json result carries an error" do
54
+ lambda { Boss::ResultFactory.build(error_json_result) }.should raise_error(Boss::BossError)
55
+ end
56
+
57
+ end
data/spec/spec.opts ADDED
@@ -0,0 +1,2 @@
1
+ --colour
2
+ --diff
@@ -0,0 +1,7 @@
1
+ require 'rubygems'
2
+ gem 'rspec'
3
+ require 'spec'
4
+
5
+ $:.unshift(File.join(File.dirname(__FILE__), %w[.. lib]))
6
+
7
+ require 'boss'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: eshopworks-rboss
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.5
4
+ version: 0.1.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joseph Wilk
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2008-09-13 00:00:00 -07:00
12
+ date: 2008-10-17 00:00:00 -07:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -46,7 +46,7 @@ dependencies:
46
46
  requirements:
47
47
  - - ">="
48
48
  - !ruby/object:Gem::Version
49
- version: 1.7.0
49
+ version: 1.8.0
50
50
  version:
51
51
  description: Api wrapping Yahoo Boss search
52
52
  email:
@@ -55,10 +55,50 @@ executables: []
55
55
 
56
56
  extensions: []
57
57
 
58
- extra_rdoc_files: []
59
-
60
- files: []
61
-
58
+ extra_rdoc_files:
59
+ - History.txt
60
+ - License.txt
61
+ - Manifest.txt
62
+ - README.txt
63
+ - TODO.txt
64
+ files:
65
+ - History.txt
66
+ - License.txt
67
+ - Manifest.txt
68
+ - README.txt
69
+ - Rakefile
70
+ - TODO.txt
71
+ - config/hoe.rb
72
+ - config/requirements.rb
73
+ - features/steps/web_search_steps.rb
74
+ - features/web_search.feature
75
+ - gem_tasks/cucumber.rake
76
+ - gem_tasks/deployment.rake
77
+ - gem_tasks/environment.rake
78
+ - gem_tasks/fix_cr_lf.rake
79
+ - gem_tasks/gemspec.rake
80
+ - gem_tasks/rspec.rake
81
+ - gem_tasks/verify_rcov.rake
82
+ - gem_tasks/website.rake
83
+ - lib/boss.rb
84
+ - lib/boss/api.rb
85
+ - lib/boss/config.rb
86
+ - lib/boss/result.rb
87
+ - lib/boss/result/base.rb
88
+ - lib/boss/result/image.rb
89
+ - lib/boss/result/news.rb
90
+ - lib/boss/result/spell.rb
91
+ - lib/boss/result/web.rb
92
+ - lib/boss/result_collection.rb
93
+ - lib/boss/result_factory.rb
94
+ - lib/boss/version.rb
95
+ - rboss.gemspec
96
+ - spec/boss/api_spec.rb
97
+ - spec/boss/config_spec.rb
98
+ - spec/boss/result_collection_spec.rb
99
+ - spec/boss/result_factory_spec.rb
100
+ - spec/spec.opts
101
+ - spec/spec_helper.rb
62
102
  has_rdoc: true
63
103
  homepage: http://github.com/eshopworks/rboss-gem
64
104
  post_install_message: