eshopworks-rboss 0.1.5 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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: