├── .gitignore
├── .travis.yml
├── Gemfile
├── README.md
├── Rakefile
├── config
└── isbndb.example.yml
├── isbndb.gemspec
├── lib
├── core_extensions
│ ├── nil.rb
│ └── string.rb
├── isbndb.rb
└── isbndb
│ ├── access_key_set.rb
│ ├── exceptions.rb
│ ├── query.rb
│ ├── result.rb
│ └── result_set.rb
└── spec
├── responses
├── access_key_error.xml
├── authors_seth.xml
├── books_hello.xml
├── categories_fiction.xml
├── keystats.xml
├── publishers_francis.xml
├── search.xml
├── single_book.xml
└── subjects_ruby.xml
├── spec_helper.rb
├── support
└── helpers.rb
└── units
├── access_key_set_spec.rb
├── nil_spec.rb
├── query_spec.rb
├── result_set_spec.rb
├── result_spec.rb
└── string_spec.rb
/.gitignore:
--------------------------------------------------------------------------------
1 | *.gem
2 | *.rbc
3 | .bundle
4 | .config
5 | .yardoc
6 | Gemfile.lock
7 | InstalledFiles
8 | _yardoc
9 | coverage
10 | doc/
11 | lib/bundler/man
12 | pkg
13 | rdoc
14 | spec/reports
15 | test/tmp
16 | test/version_tmp
17 | tmp
18 | bin/
19 |
20 | config/isbndb.yml
21 | *~
22 |
23 | .rvmrc
24 | .rspec
25 | .ruby-version
26 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | rvm:
2 | - 1.9.3
3 | - 2.0.0
4 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 | gemspec
3 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Ruby ISBNdb
2 | ===========
3 | Ruby ISBNdb is a simple, Ruby library that connects to [ISBNdb.com's Web Service](http://isbndb.com) and API. Ruby ISBNdb is written to mimic the ease of ActiveRecord and other ORM programs, without all the added hassles. It's still in beta phases, but it is almost fully functional for the basic search features of ISBNdb.
4 |
5 | Version 1.x
6 | -----------
7 | *ISBNdb 1.x.x has been deprecated!*. You should upgrade to the new version as soon as possible. The old documentation is still available in the [git history](https://github.com/sethvargo/isbndb/tree/75cfe76d096f92b2dfaf1c1b42d7c84ff86fcbc0). There are *significant* changes in this new version, so please test appropriately.
8 |
9 | Installation
10 | ------------
11 | To get started, install the gem:
12 |
13 | gem install isbndb
14 |
15 | Alternatively, you can add it to your Gemfile:
16 |
17 | ```ruby
18 | gem 'isbndb', '~> 2.0.0'
19 | ```
20 |
21 | Basic Setup
22 | -----------
23 | To get started, you'll need to create a `config/isbndb.yml` file in your project root. It should look like this:
24 |
25 | ```yml
26 | access_keys:
27 | - KEY_1
28 | - KEY_2
29 | ...
30 | ```
31 |
32 | Where you list your access keys. This was in response to security holes in version 1.x where values were passed directly to the initializer.
33 |
34 | Now you're ready to get started:
35 |
36 | ```ruby
37 | @query = ISBNdb::Query.find_book_by_title('Ruby')
38 | ```
39 |
40 | ActiveRecord-like Usage
41 | -----------------------
42 | Another reason why you'll love Ruby ISBNdb is it's similarity to ActiveRecord. In fact, it's *based* on ActiveRecord, so it should look similar. It's best to lead by example, so here are a few ways to search for books, authors, etc:
43 |
44 | ```ruby
45 | ISBNdb::Query.find_book_by_isbn("978-0-9776-1663-3")
46 | ISBNdb::Query.find_books_by_title("Agile Development")
47 | ISBNdb::Query.find_author_by_name("Seth Vargo")
48 | ISBNdb::Query.find_publisher_by_name("Pearson")
49 | ```
50 |
51 | Advanced Usage
52 | --------------
53 | Additionally, you can also use a more advanced syntax for complete control:
54 |
55 | ```ruby
56 | ISBNdb::Query.find(:collection => 'books', :where => { :isbn => '978-0-9776-1663-3' })
57 | ISBNdb::Query.find(:collection => 'books', :where => { :author => 'Seth Vargo' }, :results => 'prices')
58 | ```
59 |
60 | Options for `:collection` include **books**, **subjects**, **categories**, **authors**, and **publishers**.
61 |
62 | If you are unfamiliar with some of these options, have a look at the [ISBNdb API](http://isbndb.com/docs/api/)
63 |
64 | Processing Results
65 | ------------------
66 | A `ResultSet` is nothing more than an enhanced array of `Result` objects. The easiest way to process results from Ruby ISBNdb is most easily done using the `.each` method.
67 |
68 | ```ruby
69 | results = ISBNdb::Query.find_books_by_title("Agile Development")
70 | results.each do |result|
71 | puts "title: #{result.title}"
72 | puts "isbn10: #{result.isbn}"
73 | puts "authors: #{result.authors_text}"
74 | end
75 | ```
76 |
77 | **Note**: calling a method on a `Result` object that is `empty?`, `blank?`, or `nil?` will *always* return `nil`. This was a calculated decision so that developers can do the following:
78 |
79 | ```ruby
80 | puts "title: #{result.title}" unless result.title.nil?
81 | ```
82 |
83 | versus
84 |
85 | ```ruby
86 | puts "title: #{result.title}" unless result.title.nil? || result.title.blank? || result.title.empty?
87 | ```
88 |
89 | because ISBNdb.com API is generally inconsistent with respect to returning empty strings, whitespace characters, or nothing at all.
90 |
91 | **Note**: XML-keys to method names are inversely mapped. CamelCased XML keys and attributes (like BookData or TitleLong) are converted to lowercase under_scored methods (like book_data or title_long). ALL XML keys and attributes are mapped in this way.
92 |
93 | Pagination
94 | ----------
95 | Pagination is based on the `ResultSet` object. The `ResultSet` object contains the methods `go_to_page`, `next_page`, and `prev_page`... Their function should not require too much explanation. Here's a basic example:
96 |
97 | ```ruby
98 | results = ISBNdb::Query.find_books_by_title("ruby")
99 | results.next_page.each do |result|
100 | puts "title: #{result.title}"
101 | end
102 | ```
103 |
104 | A more realistic example - getting **all** books of a certain title:
105 |
106 | ```ruby
107 | results = ISBNdb::Query.find_books_by_title("ruby")
108 | while results
109 | results.each do |result|
110 | puts "title: #{title}"
111 | end
112 |
113 | results = results.next_page
114 | end
115 | ```
116 |
117 | It seems incredibly unlikely that a developer would ever use `prev_page`, but it's still there if you need it.
118 |
119 | Because there may be cases where a developer may need a specific page, the `go_to_page` method also exists. Consider an example where you batch-process books into your own database (which is probably against Copyright laws, but you don't seem to care...):
120 |
121 | ```ruby
122 | results = ISBNdb::Query.find_books_by_title("ruby")
123 | results = results.go_to_page(50) # where 50 is the page number you want
124 | ```
125 |
126 | **Note**: `go_to_page`, `next_page` and `prev_page` return `nil` if the `ResultSet` is out of `Result` objects. If you try something like `results.next_page.next_page`, you could get a whiny nil. Think `LinkedLists` when working with `go_to_page`, `next_page` and `prev_page`.
127 |
128 | **BIGGER NOTE**: `go_to_page`, `next_page` and `prev_page` BOTH make a subsequent call to the API, using up one of your 500 daily request limits. Please keep this in mind!
129 |
130 | Advanced Key Management
131 | -----------------------
132 | As over version 2.0, all access key management has moved into the `config/isbndb.yml` file. ISBNdb will auto-rollover if you specify multiple keys.
133 |
134 | Statistics
135 | ----------
136 | Ruby ISBNdb now supports basic statistics (from the server):
137 |
138 | ```ruby
139 | ISBNdb::Query.keystats # => {:requests => 50, :granted => 49}
140 | ISBNdb::Query.keystats[:granted] # => 49
141 | ```
142 |
143 | **Note**: Ironically, this information also comes from the server, so it counts as a request...
144 |
145 | Exceptions
146 | ----------
147 | Ruby ISBNdb could raise the following possible exceptions:
148 |
149 | ```ruby
150 | ISBNdb::AccessKeyError
151 | ```
152 |
153 | You will most likely encounter `ISBNdb::AccessKeyError` when you have reached your 500-request daily limit. `ISBNdb::InvalidURIError` usually occurs when using magic finder methods with typographical errors.
154 |
155 | A Real-Life Example
156 | -------------------
157 | Here is a real-life example of how to use Ruby ISBNdb. Imagine a Rails application that recommends books. You have written a model, `Book`, that has a variety of methods. One of those class methods, `similar`, returns a list of book isbn values that are similar to the current book. Here's how one may do that:
158 |
159 | ```ruby
160 | # books_controller.rb
161 | def simliar
162 | @book = Book.find(params[:id])
163 | @query = ISBNdb::Query.new(['API-KEY-1', 'API-KEY-2'])
164 | @isbns = @book.similar # returns an array like [1234567890, 0987654321, 3729402827...]
165 |
166 | @isbns.each do |isbn|
167 | begin
168 | (@books ||= []) << ISBNdb::Query.find_book_by_isbn(isbn).first
169 | rescue ISBNdb::AccessKeyError
170 | SomeMailer.send_limit_email.deliver!
171 | end
172 | end
173 | end
174 | ```
175 |
176 | ```ruby
177 | # similar.html.erb
178 |
The following books are recommeded for you:
179 | <% @books.each do |book| %>
180 |
181 |
<%= book.title_long %>
182 |
authors: <%= book.authors_text %>
183 |
184 | <% end %>
185 | ```
186 |
187 | Testing
188 | -------
189 | [](http://travis-ci.org/sethvargo/isbndb)
190 |
191 | Change Log
192 | ----------
193 | 2012-6-17 - Released v2.0
194 | 2011-3-11 - Officially changed from ruby_isbndb to isbndb with special thanks to [Terje Tjervaag](https://github.com/terje) for giving up the gem name :)
195 |
196 | Acknowledgments
197 | ----------------
198 | Special thanks to Terje Tjervaag (https://github.com/terje) for giving up the gem name 'isbndb'!
199 |
200 | Special thanks to Lazlo (https://github.com/lazlo) for forwarding his project here!
201 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | require 'bundler/gem_tasks'
2 | require 'rspec/core/rake_task'
3 |
4 | RSpec::Core::RakeTask.new(:spec)
5 |
6 | task :setup do
7 | FileUtils.cp('config/isbndb.example.yml', 'config/isbndb.yml')
8 | end
9 |
10 | task default: [:setup, :spec]
11 |
--------------------------------------------------------------------------------
/config/isbndb.example.yml:
--------------------------------------------------------------------------------
1 | access_keys:
2 | - ABC123
3 | - DEF456
4 |
--------------------------------------------------------------------------------
/isbndb.gemspec:
--------------------------------------------------------------------------------
1 | # encoding: utf-8
2 | $:.push File.expand_path('../lib', __FILE__)
3 |
4 | Gem::Specification.new do |s|
5 | s.name = 'isbndb'
6 | s.version = '2.0.2'
7 | s.author = 'Seth Vargo'
8 | s.email = 'sethvargo@gmail.com'
9 | s.homepage = 'https://github.com/sethvargo/isbndb'
10 | s.summary = 'Connect with ISBNdb.com\'s API'
11 | s.description = 'Ruby ISBNdb is a amazingly fast and accurate gem that reads ISBNdb.com\'s XML API and gives you incredible flexibilty with the results! The newest version of the gem also features caching, so developers minimize API-key usage.'
12 |
13 | s.files = `git ls-files`.split("\n")
14 | s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
15 | s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
16 | s.require_paths = ["lib"]
17 |
18 | # Runtime dependencies
19 | s.add_runtime_dependency 'httparty', '~> 0.12'
20 | s.add_runtime_dependency 'rake'
21 |
22 | # Development dependencies
23 | s.add_development_dependency 'rspec', '~> 2.14'
24 | s.add_development_dependency 'webmock', '~> 1.15'
25 | end
26 |
--------------------------------------------------------------------------------
/lib/core_extensions/nil.rb:
--------------------------------------------------------------------------------
1 | class NilClass
2 | def blank?
3 | true
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/lib/core_extensions/string.rb:
--------------------------------------------------------------------------------
1 | class String
2 | require 'date'
3 |
4 | def is_plural?
5 | self.downcase.pluralize == self.downcase
6 | end
7 |
8 | def is_singular?
9 | !self.is_plural?
10 | end
11 |
12 | def titleize
13 | str = self[0].upcase + self[1..-1].downcase
14 | end
15 |
16 | def singularize
17 | str = self.dup
18 | if str[-3..-1] == 'ies'
19 | str[0..-4] + 'y'
20 | elsif str[-1] == 's'
21 | str[0..-2]
22 | else
23 | str
24 | end
25 | end
26 |
27 | def pluralize
28 | str = self.dup
29 | if str[-1] == 'y'
30 | str[0..-2] + 'ies'
31 | elsif str[-1] == 's'
32 | str
33 | else
34 | str + 's'
35 | end
36 | end
37 |
38 | def blank?
39 | dup.strip.length == 0 ? true : false
40 | end
41 |
42 | def underscore
43 | self.dup.gsub(/::/, '/').
44 | gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
45 | gsub(/([a-z\d])([A-Z])/,'\1_\2').
46 | tr("-", "_").
47 | downcase
48 | end
49 | end
50 |
--------------------------------------------------------------------------------
/lib/isbndb.rb:
--------------------------------------------------------------------------------
1 | module ISBNdb
2 | require 'httparty'
3 |
4 | require 'isbndb/access_key_set'
5 | require 'isbndb/exceptions'
6 | require 'isbndb/query'
7 | require 'isbndb/result_set'
8 | require 'isbndb/result'
9 |
10 | require 'core_extensions/string'
11 | require 'core_extensions/nil'
12 | end
13 |
--------------------------------------------------------------------------------
/lib/isbndb/access_key_set.rb:
--------------------------------------------------------------------------------
1 | module ISBNdb
2 | protected
3 | # The AccessKeySet is a simple class used to manage access keys. It is used primarily
4 | # by the ruby_isbndb class to automatically advance access keys when necessary.
5 | class AccessKeySet
6 | # Create the @access_keys array and then verify that the keys are valid keys.
7 | def initialize
8 | @access_keys ||= YAML::load(File.open('config/isbndb.yml'))['access_keys']
9 | end
10 |
11 | # Returns the total number of access keys in this set.
12 | def size
13 | @access_keys.size
14 | end
15 |
16 | def current_index
17 | @current_index ||= 0
18 | end
19 |
20 | # Get the current key. It returns a string of the access key.
21 | def current_key
22 | @access_keys[current_index]
23 | end
24 |
25 | # Move the key pointer forward.
26 | def next_key!
27 | @current_index = current_index + 1
28 | current_key
29 | end
30 |
31 | # Get the next key.
32 | def next_key
33 | @access_keys[current_index+1]
34 | end
35 |
36 | # Move the key pointer back.
37 | def prev_key!
38 | @current_index = current_index - 1
39 | current_key
40 | end
41 |
42 | # Get the previous key.
43 | def prev_key
44 | @access_keys[current_index-1]
45 | end
46 |
47 | # Tell Ruby ISBNdb to use a specified key. If the key does not exist, it is
48 | # added to the set and set as the current key.
49 | def use_key(key)
50 | @current_index = @access_keys.index(key) || @access_keys.push(key).index(key)
51 | current_key
52 | end
53 |
54 | # Remove the given access key from the AccessKeySet.
55 | def remove_key(key)
56 | @access_keys.delete(key)
57 | end
58 |
59 | # Pretty print the AccessKeySet
60 | def to_s
61 | "#"
62 | end
63 | end
64 | end
65 |
--------------------------------------------------------------------------------
/lib/isbndb/exceptions.rb:
--------------------------------------------------------------------------------
1 | module ISBNdb
2 | class AccessKeyError < StandardError; end
3 | end
4 |
--------------------------------------------------------------------------------
/lib/isbndb/query.rb:
--------------------------------------------------------------------------------
1 | module ISBNdb
2 | # The Query object is the most important class of the ISBNdb Module. It is the only public
3 | # class, and handles the processing power.
4 | class Query
5 | include HTTParty
6 | base_uri 'http://isbndb.com/api'
7 | headers 'Content-Type' => 'text/xml'
8 |
9 | # This is the generic find method. It accepts a hash of parameters including :collection,
10 | # :where clauses, and :results to show. It builds the corresponding URI and sends that URI
11 | # off to the ResultSet for processing.
12 | def self.find(params = {})
13 | raise 'No parameters specified! You must specify at least one parameter!' unless params[:where]
14 | raise 'params[:where] cannot be a String! It must be a Hash!' if params[:where].is_a?(String)
15 | raise 'params[:where] cannot be an Array! It must be a Hash!' if params[:where].is_a?(Array)
16 |
17 | collection = params[:collection] ||= :books
18 | results = params[:results] ||= :details
19 | results = [results].flatten
20 |
21 | # build the search clause
22 | searches = []
23 | params[:where].each_with_index do |(key,val), i|
24 | searches << "index#{i+1}=#{key.to_s.strip}"
25 | searches << "value#{i+1}=#{val.to_s.strip}"
26 | end
27 |
28 | # make the request
29 | uri = "/#{collection}.xml?access_key=#{access_key_set.current_key}&results=#{results.join(',')}{searches.join('&')}"
30 | ISBNdb::ResultSet.new(uri, collection)
31 | rescue ISBNdb::AccessKeyError
32 | access_key_set.next_key!
33 | retry unless access_key_set.current_key.nil?
34 | raise
35 | end
36 |
37 | # This method returns keystats about your API key, including the number of requests
38 | # and the number of granted requets. Be advised that this request actually counts
39 | # as a request to the server, so use with caution.
40 | def self.keystats
41 | result = self.get("/books.xml?access_key=#{access_key_set.current_key}&results=keystats")
42 | result.parsed_response['ISBNdb']['KeyStats'] || {}
43 | end
44 |
45 | # Method missing allows for dynamic finders, similar to that of ActiveRecord. See
46 | # the README for more information on using magic finders.
47 | def self.method_missing(m, *args, &block)
48 | method = m.to_s.downcase
49 |
50 | if method.match(/find_(.+)_by_(.+)/)
51 | split = method.split('_', 4)
52 | collection, search_strs = split[1].downcase.pluralize, [split.last]
53 |
54 | # check and see if we are searching multiple fields
55 | search_strs = search_strs.first.split('_and_') if(search_strs.first.match(/_and_/))
56 | raise "Wrong Number of Arguments (#{args.size} for #{search_strs.size})" if args.size != search_strs.size
57 |
58 | # create the searches hash
59 | searches = {}
60 | search_strs.each_with_index { |str, i| searches[str.strip.to_sym] = args[i].strip }
61 |
62 | return find(:collection => collection, :where => searches)
63 | end
64 |
65 | super
66 | end
67 |
68 | # Pretty print the Query object with the access key.
69 | def self.to_s
70 | "#"
71 | end
72 |
73 | private
74 | def self.access_key_set
75 | @@access_key_set ||= ISBNdb::AccessKeySet.new
76 | end
77 | end
78 | end
79 |
--------------------------------------------------------------------------------
/lib/isbndb/result.rb:
--------------------------------------------------------------------------------
1 | module ISBNdb
2 |
3 | private
4 | # The Result object is a true testament of metaprogramming. Almost every method of the Result
5 | # is dynamically generated through the build_result() method. All attribtues of the XML are
6 | # parsed, translated, and populated as instance methods on the Result object. This allows for
7 | # easy Ruby-like access (@book.title), without hardcoding every single possible return value
8 | # from the ISBNdb API
9 | class Result
10 | # Initialize simply calls build_result. Because the method definition is recusive, it must
11 | # be moved into a separate helper.
12 | def initialize(json = {})
13 | @store = build_result(json)
14 | end
15 |
16 | # Because a result may or may not contain a specified key, we always return nil for
17 | # consistency. This allows developers to easily check for .nil? instead of checking for
18 | # a myriad of exceptions throughout their code.
19 | def method_missing(m, *args, &block)
20 | @store[m.to_s.underscore]
21 | end
22 |
23 | # Return a list of all "methods" this class responds to
24 | def instance_methods
25 | @store.collect{ |key,value| key.to_s }
26 | end
27 |
28 | # Pretty print the Result including the number of singleton methods that exist. If
29 | # you want the ACTUAL singleton methods, call @result.singleton_methods.
30 | def to_s
31 | "#"
32 | end
33 |
34 | def inspect
35 | "# ' + value.inspect }.join(', ')}>"
36 | end
37 |
38 | def ==(result)
39 | self.inspect == result.inspect
40 | end
41 |
42 | private
43 | def build_result(json)
44 | result = {}
45 |
46 | json.each do |key,value|
47 | result[key.to_s.underscore] = if value.is_a?(Hash)
48 | build_result(value)
49 | elsif value.blank?
50 | nil
51 | else
52 | value
53 | end
54 | end
55 |
56 | result
57 | end
58 | end
59 | end
60 |
--------------------------------------------------------------------------------
/lib/isbndb/result_set.rb:
--------------------------------------------------------------------------------
1 | module ISBNdb
2 |
3 | private
4 | # The ResultSet is a collection of Result objects with helper methods for pagination. It
5 | # allows for easy paginating through multiple pages of results as well as jumping to a
6 | # specific results page.
7 | class ResultSet
8 | include Enumerable
9 | include HTTParty
10 |
11 | base_uri 'http://isbndb.com/api'
12 | headers 'Content-Type' => 'text/xml'
13 |
14 | # This method creates instance variables for @uri, @collection, and @current_page. It then
15 | # attempts to parse the XML at the gieven URI. If it cannot parse the URI for any reason,
16 | # it will raise an ISBNdb::InvalidURIError. Next, the results are checked for an any error
17 | # messages. An ISBNdb::AccessKeyError will be raised if the results contain any errors.
18 | # Finally, this method then actually builds the ResultSet.
19 | def initialize(uri, collection, current_page = 1)
20 | @uri = URI.escape(uri)
21 | @collection = collection.to_s.titleize.singularize
22 | @current_page = current_page
23 | @parsed_response = self.class.get(@uri).parsed_response['ISBNdb']
24 |
25 | check_results
26 | build_results
27 | end
28 |
29 | def size
30 | @results.size
31 | end
32 |
33 | # Because ResultSet extends Enumerable, we need to define the each method. This allows users
34 | # to call methods like .first, .last, and .each on the ResultSet, making it behave like
35 | # a primitive array.
36 | def each(&block)
37 | @results.each &block
38 | end
39 |
40 | # Access via index
41 | def [](i)
42 | @results[i]
43 | end
44 |
45 | # Jump to a specific page. This method will return nil if the specified page does not exist.
46 | def go_to_page(page)
47 | get_total_pages unless @total_pages
48 | return nil if page.to_i < 1 || page.to_i > @total_pages
49 | ISBNdb::ResultSet.new("#{@uri}&page_number=#{page}", @collection, page)
50 | end
51 |
52 | # Go to the next page. This method will return nil if a next page does not exist.
53 | def next_page
54 | go_to_page(@current_page+1)
55 | end
56 |
57 | # Go to the previous page. This method will return nil if a previous page does not exist.
58 | def prev_page
59 | go_to_page(@current_page-1)
60 | end
61 |
62 | # Pretty prints the Result set information.
63 | def to_s
64 | "# #{@results.size}>"
65 | end
66 |
67 | def ==(result_set)
68 | self.size == result_set.size && self.instance_variable_get('@results') == result_set.instance_variable_get('@results')
69 | end
70 |
71 | private
72 | # Check the results for an error message. If one exists, raise an ISBNdb::AccessKeyError for now.
73 | # Currently the API does not differentiate between an overloaded API key and an invalid one
74 | # (it returns the same exact response), so there only exists one exception for now...
75 | def check_results
76 | raise ISBNdb::AccessKeyError if @parsed_response['ErrorMessage']
77 | end
78 |
79 | # Iterate over #{@collection}List/#{@collection}Data (ex. BookList/BookData) and build a result with
80 | # each child. This method works because the API always returns #{@collection}List followed by a subset
81 | # of #{@collection}Data. These results are all pushed into the @results array for accessing.
82 | def build_results
83 | result_json = @parsed_response["#{@collection}List"]["#{@collection}Data"]
84 | if result_json.is_a?(Hash) ## One result, typically from find_by_isbn
85 | @results = [Result.new(result_json)]
86 | else
87 | @results = (result_json || []).collect{ |json| Result.new(json) }
88 | end
89 | end
90 |
91 | # This helper method is mainly designed for use with the go_to_page(page) method. It parses the XML
92 | # and returns the total number of pages that exist for this result set.
93 | def get_total_pages
94 | list = @parsed_response["#{@collection}List"]
95 | @total_pages = (list['total_results'].to_f/list['page_size'].to_f).ceil
96 | end
97 | end
98 | end
99 |
--------------------------------------------------------------------------------
/spec/responses/access_key_error.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Access key error
5 |
6 |
--------------------------------------------------------------------------------
/spec/responses/authors_seth.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Abraham, Seth
7 |
8 |
9 |
10 | Abramson, Seth F.
11 |
12 |
13 |
14 | Adams, Seth
15 |
16 |
17 |
18 | Seth Afikorah-Danquah
19 |
20 |
21 |
22 | Agata, Seth H
23 |
24 |
25 |
26 | Agbo, Seth A.
27 |
28 |
29 |
30 | Allcorn, Seth
31 |
32 |
33 |
34 | Ames, Seth
35 |
36 |
37 |
38 | Anderson, Seth B.
39 |
40 |
41 |
42 | Seth Andrew
43 |
44 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/spec/responses/books_hello.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | 100th Day of School
7 | 100th Day of School (Hello Reader Level 2)
8 | Angela Shelf Medearis,
9 | Fitzgerald Books
10 |
11 |
12 |
13 | 100th Day, The
14 | 100th Day, The (level 1) (Hello Reader Level 1)
15 | Alayne Pick, Grace Maccarone, Laura Freeman (Illustrator)
16 | Cartwheel
17 |
18 |
19 |
20 | 2011 Hello Kitty Engagement Calendar
21 |
22 | Day Dream (Contributor)
23 | Day Dream
24 |
25 |
26 |
27 | 2011 Hello Kitty Wall Calendar
28 |
29 | Day Dream (Contributor)
30 | Day Dream
31 |
32 |
33 |
34 | 2012 Hello Kitty 2 Year Pocket Planner Calendar
35 |
36 | Day Dream,
37 | Day Dream
38 |
39 |
40 |
41 | 2012 Hello Kitty Juvenile Activity Calendar
42 |
43 | Day Dream,
44 | Day Dream
45 |
46 |
47 |
48 | 2012 Hello Kitty Mini Calendar
49 |
50 | Day Dream,
51 | Day Dream
52 |
53 |
54 |
55 | 2012 Hello Kitty Wall Calendar
56 |
57 | Day Dream,
58 | Day Dream
59 |
60 |
61 |
62 | 2012 Hello Kitty Weekly Engagement Calendar
63 |
64 | Day Dream,
65 | Day Dream
66 |
67 |
68 |
69 | Hello gorgeous
70 | Hello gorgeous: a guide to style
71 | by Kristen Kemp
72 | New York : Scholastic, c2000.
73 |
74 |
75 |
76 |
77 |
--------------------------------------------------------------------------------
/spec/responses/categories_fiction.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Fiction
7 |
8 |
9 |
10 | Fiction
11 |
12 |
13 |
14 | Fiction
15 |
16 |
17 |
18 | Fiction
19 |
20 |
21 |
22 | Fiction
23 |
24 |
25 |
26 | Fiction
27 |
28 |
29 |
30 | Fiction
31 |
32 |
33 |
34 | Fiction
35 |
36 |
37 |
38 | Fiction
39 |
40 |
41 |
42 | Fiction
43 |
44 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/spec/responses/keystats.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/spec/responses/publishers_francis.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | : Taylor & Francis
7 |
8 |
9 |
10 | A.A. Balkema/Taylor & Francis
11 |
12 |
13 |
14 | Albert Francis Perrin
15 |
16 |
17 |
18 | Auerbach Publications, Taylor & Francis Group
19 |
20 |
21 |
22 | Auerbach Publications/Taylor & Francis
23 |
24 |
25 |
26 | Auerbach Publications/Taylor & Francis Group
27 |
28 |
29 |
30 | Auerbach/Taylor&Francis
31 |
32 |
33 |
34 | B. Francis
35 |
36 |
37 |
38 | Balkema/Taylor & Francis
39 |
40 |
41 |
42 | Beau Francis Press
43 |
44 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/spec/responses/search.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | 1000s of Amazing Uses for Everyday Products
7 |
8 | Betsy Rossen Elliot,
9 | Publications International, Ltd.
10 |
11 |
12 |
13 | 1000 more amazing facts for kids
14 |
15 | Keith Smithcartoons by David Mostyn
16 | London Ward Lock c1986
17 |
18 |
19 |
20 | 1001 Amazing Animal Facts
21 |
22 | Marc Powell,
23 | Arcturus Publishing Ltd
24 |
25 |
26 |
27 | 1001 Amazing Facts
28 |
29 |
30 | Egmont Childrens Books
31 |
32 |
33 |
34 | 1001 Stupid Sports Quotes
35 | 1001 Stupid Sports Quotes: Jaw-Dropping, Stupefying, and Amazing Expressions from the World's Best Athletes
36 | Randy Howe (Editor)
37 | The Lyons Press
38 |
39 |
40 |
41 | 100 Amazing Americans
42 |
43 |
44 | The Trumpet Club
45 |
46 |
47 |
48 | 100 amazing answers to prayer
49 |
50 | William J. Petersen and Randy Petersen
51 | Grand Rapids, MI : F.H. Revell, c2003.
52 |
53 |
54 |
55 | 100 Amazing Answers to Prayer
56 |
57 | William J. Petersen, Randy Petersen,
58 | Revell
59 |
60 |
61 |
62 | 100 Amazing Award-Winning Science Fair Projects
63 |
64 | Glen Vecchione,
65 | Sterling
66 |
67 |
68 |
69 | 100 Amazing Crosswords
70 |
71 | Thomas Joseph,
72 | Puzzlewright
73 |
74 |
75 |
76 |
77 |
--------------------------------------------------------------------------------
/spec/responses/single_book.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | 100th Day of School
7 | 100th Day of School (Hello Reader Level 2)
8 | Angela Shelf Medearis,
9 | Fitzgerald Books
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/spec/responses/subjects_ruby.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | ruby akademicki
7 |
8 |
9 | Bell, Ruby
10 |
11 |
12 | Bell, Ruby,1942
13 |
14 |
15 | Braff, Ruby,1927-2003
16 |
17 |
18 | Bridges, Ruby
19 |
20 |
21 | Bridges, Ruby -- Comic books, strips, etc
22 |
23 |
24 | Crane, Ruby (Fictitious character) -- Fiction
25 |
26 |
27 | Cress, Ruby,1911-Correspondence
28 |
29 |
30 | Dee, Ruby
31 |
32 |
33 | Duncan, Ruby
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/spec/spec_helper.rb:
--------------------------------------------------------------------------------
1 | require 'rspec'
2 | require 'webmock/rspec'
3 |
4 | Dir[File.expand_path('spec/support/**/*.rb')].each { |f| require f }
5 |
6 | require 'isbndb'
7 |
8 | RSpec.configure do |config|
9 | config.include Helpers
10 | end
11 |
--------------------------------------------------------------------------------
/spec/support/helpers.rb:
--------------------------------------------------------------------------------
1 | module Helpers
2 | # Stub YAML::load to return the hash of keys that we specify
3 | def stub_access_keys(*keys)
4 | YAML.stub(:load) do
5 | { 'access_keys' => keys }
6 | end
7 | end
8 | end
9 |
--------------------------------------------------------------------------------
/spec/units/access_key_set_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe ISBNdb::AccessKeySet do
4 | before do
5 | stub_access_keys('ABC123', 'DEF456', 'GHI789')
6 | @access_key_set = ISBNdb::AccessKeySet.new
7 | end
8 |
9 | context 'size' do
10 | it 'should return 0 if there are no keys' do
11 | stub_access_keys
12 | ISBNdb::AccessKeySet.new.size.should == 0
13 | end
14 |
15 | it 'should return the number of access keys' do
16 | @access_key_set.size.should == 3
17 | end
18 | end
19 |
20 | context 'current_index' do
21 | it 'should return the current index' do
22 | @access_key_set.current_index.should == 0
23 | end
24 | end
25 |
26 | context 'current_key' do
27 | it 'should return the current key' do
28 | @access_key_set.current_key.should == 'ABC123'
29 | end
30 | end
31 |
32 | context 'next_key!' do
33 | it 'should advance to the next key' do
34 | expect{ @access_key_set.next_key! }.to change{ @access_key_set.current_key }.from('ABC123').to('DEF456')
35 | end
36 |
37 | it 'should return the new key' do
38 | @access_key_set.next_key!.should == 'DEF456'
39 | end
40 | end
41 |
42 | context 'next_key' do
43 | it 'should return the next key' do
44 | @access_key_set.next_key.should == 'DEF456'
45 | end
46 | end
47 |
48 | context 'prev_key!' do
49 | before do
50 | @access_key_set.instance_variable_set('@current_index', 1)
51 | end
52 |
53 | it 'should de-advance to the prev key' do
54 | expect{ @access_key_set.prev_key! }.to change{ @access_key_set.current_key }.from('DEF456').to('ABC123')
55 | end
56 |
57 | it 'should return the new key' do
58 | @access_key_set.prev_key!.should == 'ABC123'
59 | end
60 | end
61 |
62 | context 'prev_key' do
63 | before do
64 | @access_key_set.instance_variable_set('@current_index', 1)
65 | end
66 |
67 | it 'should return the prev key' do
68 | @access_key_set.prev_key.should == 'ABC123'
69 | end
70 | end
71 |
72 | context 'use_key' do
73 | it 'should use an existing key' do
74 | @access_key_set.use_key('GHI789').should == 'GHI789'
75 | end
76 |
77 | it 'should create a new key if it does not already exist' do
78 | @access_key_set.use_key('NEW_KEY').should == 'NEW_KEY'
79 | @access_key_set.instance_variable_get('@access_keys').should include('NEW_KEY')
80 | end
81 | end
82 |
83 | context 'remove_key' do
84 | it 'should do nothing if the key does not exist' do
85 | expect{ @access_key_set.remove_key('NOPE') }.not_to change{ @access_key_set.instance_variable_get('@access_keys') }
86 | end
87 |
88 | it 'should remove the key if it exists' do
89 | expect{ @access_key_set.remove_key('ABC123') }.to change{ @access_key_set.instance_variable_get('@access_keys') }.from(['ABC123', 'DEF456', 'GHI789']).to(['DEF456', 'GHI789'])
90 | end
91 | end
92 |
93 | context 'to_s' do
94 | it 'should return the correct string' do
95 | @access_key_set.to_s.should == '#'
96 | end
97 | end
98 | end
99 |
--------------------------------------------------------------------------------
/spec/units/nil_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe NilClass do
4 | context 'blank?' do
5 | it 'should return true' do
6 | nil.blank?.should be_true
7 | end
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/spec/units/query_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe ISBNdb::Query do
4 | before do
5 | stub_access_keys('ABC123')
6 | end
7 |
8 | context 'find' do
9 | context 'without params[:where]' do
10 | it 'should throw an exception' do
11 | lambda{ ISBNdb::Query.find }.should raise_error, 'No parameters specified! You must specify at least one parameter!'
12 | end
13 | end
14 |
15 | context 'with params[:where]' do
16 | context 'as a string' do
17 | it 'should throw an exception' do
18 | lambda{ ISBNdb::Query.find(:where => 'anything') }.should raise_error, 'params[:where] cannot be a String! It must be a Hash!'
19 | end
20 | end
21 |
22 | context 'as an array' do
23 | it 'should throw an exception' do
24 | lambda{ ISBNdb::Query.find(:where => ['anything', 'else']) }.should raise_error, 'params[:where] cannot be an Array! It must be a Hash!'
25 | end
26 | end
27 |
28 | context 'as a hash' do
29 | before do
30 | stub_request(:get, "http://isbndb.com/api/books.xml?access_key=ABC123&index1=title&results=details&value1=amazing").
31 | to_return(:body => File.new('spec/responses/search.xml'), :headers => {'Content-Type'=>'text/xml'})
32 | end
33 |
34 | it 'should not throw an exception' do
35 | lambda{ ISBNdb::Query.find(:where => {:title => 'amazing'} ) }.should_not raise_error
36 | end
37 |
38 | it 'should return an ISBNdb::ResultSet' do
39 | ISBNdb::Query.find(:where => {:title => 'amazing'}).should be_a(ISBNdb::ResultSet)
40 | end
41 | end
42 |
43 | context 'with an access key error' do
44 | before do
45 | stub_request(:get, "http://isbndb.com/api/books.xml?access_key=ABC123&index1=title&results=details&value1=amazing").to_return(:body => File.new('spec/responses/access_key_error.xml'), :headers => {'Content-Type'=>'text/xml'})
46 | end
47 |
48 | it 'should raise an exception' do
49 | lambda{ ISBNdb::Query.find(:where => {:title => 'amazing'}) }.should raise_error ISBNdb::AccessKeyError
50 | end
51 |
52 | after do
53 | ISBNdb::Query.access_key_set.prev_key!
54 | end
55 | end
56 | end
57 | end
58 |
59 | context 'keystats' do
60 | before do
61 | stub_request(:get, 'http://isbndb.com/api/books.xml?access_key=ABC123&results=keystats').to_return(:body => File.new('spec/responses/keystats.xml'), :headers => { 'Content-Type' => 'text/xml' })
62 | end
63 |
64 | it 'should return an Hash' do
65 | ISBNdb::Query.keystats.should be_a(Hash)
66 | end
67 |
68 | it('should return the number of granted requests'){ ISBNdb::Query.keystats['granted'].should == '2' }
69 | it('should return the access key'){ ISBNdb::Query.keystats['access_key'].should == 'ABC123' }
70 | it('should return the number of requests made'){ ISBNdb::Query.keystats['requests'].should == '5' }
71 | it('should return the account limit'){ ISBNdb::Query.keystats['limit'].should == '0' }
72 | end
73 |
74 | context 'to_s' do
75 | it 'the properly formatted string' do
76 | ISBNdb::Query.to_s.should =='#'
77 | end
78 | end
79 |
80 | context 'method_missing' do
81 | context 'for a valid method it can handle' do
82 | it 'should work for books' do
83 | stub_request(:get, "http://isbndb.com/api/books.xml?access_key=ABC123&index1=title&results=details&value1=hello").to_return(:body => File.new('spec/responses/books_hello.xml'), :headers => {'Content-Type'=>'text/xml'})
84 |
85 | lambda{ ISBNdb::Query.find_book_by_title('hello') }.should_not raise_error
86 | lambda{ ISBNdb::Query.find_books_by_title('hello') }.should_not raise_error
87 |
88 | @books = ISBNdb::Query.find_books_by_title('hello')
89 | @books.size.should == 10
90 |
91 | @book = @books.first
92 | @book.book_id.should == '100th_day_of_school_a04'
93 | @book.isbn.should == '1590543947'
94 | @book.isbn13.should == '9781590543948'
95 | @book.title.should == '100th Day of School'
96 | @book.title_long.should == '100th Day of School (Hello Reader Level 2)'
97 | @book.authors_text.should == 'Angela Shelf Medearis, '
98 | @book.publisher_text.should be_a(Hash)
99 | @book.details.should be_a(Hash)
100 | end
101 |
102 | it 'should work for subjects' do
103 | stub_request(:get, "http://isbndb.com/api/subjects.xml?access_key=ABC123&index1=name&results=details&value1=Ruby").to_return(:body => File.new('spec/responses/subjects_ruby.xml'), :headers => {'Content-Type'=>'text/xml'})
104 |
105 | lambda{ ISBNdb::Query.find_subject_by_name('Ruby') }.should_not raise_error
106 | lambda{ ISBNdb::Query.find_subjects_by_name('Ruby') }.should_not raise_error
107 |
108 | @subjects = ISBNdb::Query.find_subjects_by_name('Ruby')
109 | @subjects.size.should == 10
110 |
111 | @subject = @subjects.first
112 | @subject.subject_id.should == 'ruby_napdowe_eksploatacja_podrcznik_akademicki'
113 | @subject.book_count.should == '1'
114 | @subject.marc_field.should == '650'
115 | @subject.marc_indicator_1.should be_nil
116 | @subject.marc_indicator_2.should == '9'
117 | @subject.name.should == 'ruby akademicki'
118 | end
119 |
120 | it 'should work for categories' do
121 | stub_request(:get, "http://isbndb.com/api/categories.xml?access_key=ABC123&index1=name&results=details&value1=fiction").to_return(:body => File.new('spec/responses/categories_fiction.xml'), :headers => {'Content-Type'=>'text/xml'})
122 |
123 | lambda{ ISBNdb::Query.find_category_by_name('fiction') }.should_not raise_error
124 | lambda{ ISBNdb::Query.find_categories_by_name('fiction') }.should_not raise_error
125 |
126 | @categories = ISBNdb::Query.find_categories_by_name('fiction')
127 | @categories.size.should == 10
128 |
129 | @category = @categories.first
130 | @category.category_id.should == 'society.religion.christianity.denominations.catholicism.literature.fiction'
131 | @category.parent_id.should == 'society.religion.christianity.denominations.catholicism.literature'
132 | @category.name.should == 'Fiction'
133 | @category.details.should be_a(Hash)
134 | end
135 |
136 | it 'should work for authors' do
137 | stub_request(:get, "http://isbndb.com/api/authors.xml?access_key=ABC123&index1=name&results=details&value1=Seth").to_return(:body => File.new('spec/responses/authors_seth.xml'), :headers => {'Content-Type'=>'text/xml'})
138 |
139 | lambda{ ISBNdb::Query.find_author_by_name('Seth') }.should_not raise_error
140 | lambda{ ISBNdb::Query.find_authors_by_name('Seth') }.should_not raise_error
141 |
142 | @authors = ISBNdb::Query.find_authors_by_name('Seth')
143 | @authors.size.should == 10
144 |
145 | @author = @authors.first
146 | @author.person_id.should == 'abraham_seth'
147 | @author.name.should == 'Abraham, Seth'
148 | @author.details.should be_a(Hash)
149 | end
150 |
151 | it 'should work for publishers' do
152 | stub_request(:get, "http://isbndb.com/api/publishers.xml?access_key=ABC123&index1=name&results=details&value1=Francis").to_return(:body => File.new('spec/responses/publishers_francis.xml'), :headers => {'Content-Type'=>'text/xml'})
153 |
154 | lambda{ ISBNdb::Query.find_publisher_by_name('Francis') }.should_not raise_error
155 | lambda{ ISBNdb::Query.find_publishers_by_name('Francis') }.should_not raise_error
156 |
157 | @publishers = ISBNdb::Query.find_publishers_by_name('Francis')
158 | @publishers.size.should == 10
159 |
160 | @publisher = @publishers.first
161 | @publisher.publisher_id.should == 'taylor_francis_a01'
162 | @publisher.name.should == ': Taylor & Francis'
163 | @publisher.details.should be_a(Hash)
164 | end
165 | end
166 |
167 | context 'for an invalid method it can handle' do
168 | it 'should throw an exception' do
169 | lambda{ ISBNdb::Query.find_foo_by_bar('totes') }.should raise_error
170 | end
171 | end
172 |
173 | context 'for a method is can\'t handle' do
174 | it 'should throw an exception' do
175 | lambda{ ISBNdb::Query.foo_bar }.should raise_error
176 | end
177 | end
178 | end
179 | end
180 |
--------------------------------------------------------------------------------
/spec/units/result_set_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | # These examples are used to test multiple books returned and a single book return
4 | # Actual specs at the bottom of this file
5 |
6 | shared_examples "AProperResultSet" do
7 | context 'initialize' do
8 | it 'should set all the instance variables' do
9 | @result_set.instance_variable_get('@uri').should == '/books.xml?access_key=ABC123&index1=title&results=details&value1=hello'
10 | @result_set.instance_variable_get('@collection').should == 'Book'
11 | @result_set.instance_variable_get('@current_page').should == 1
12 | @result_set.instance_variable_get('@parsed_response').should == @expected_parsed_response
13 | end
14 | end
15 |
16 | context 'size' do
17 | it 'should return the size of @results' do
18 | @result_set.size.should == @expected_result_size
19 | end
20 | end
21 |
22 | context 'each' do
23 | it 'should iterate over @results' do
24 | @result_set.each_with_index do |result,index|
25 | result.should == @result_set.instance_variable_get('@results')[index]
26 | end
27 | end
28 | end
29 |
30 | context '[]' do
31 | it 'should return the first result' do
32 | @result_set[0].should == @result_set.first
33 | end
34 | end
35 |
36 | context 'go_to_page' do
37 | it 'should get the total number of pages' do
38 | expect{ @result_set.go_to_page(1) }.to change { @result_set.instance_variable_get('@total_pages') }.to(167)
39 | end
40 |
41 | it 'should return if the page is invalid' do
42 | @result_set.go_to_page('foo').should be_nil
43 | @result_set.go_to_page(-1).should be_nil
44 | @result_set.go_to_page(138193289).should be_nil
45 | end
46 |
47 | it 'should return a new result set' do
48 | @result_set.go_to_page(2).should be_a(ISBNdb::ResultSet)
49 | @result_set.go_to_page(2).instance_variable_get('@uri').should include('page_number=2')
50 | end
51 | end
52 |
53 | context 'next_page' do
54 | it 'should go to the next page' do
55 | @result_set.next_page.should == @result_set.go_to_page(2)
56 | end
57 |
58 | it 'should return nil when there are no more pages' do
59 | @result_set.go_to_page(167).next_page.should be_nil
60 | end
61 | end
62 |
63 | context 'prev_page' do
64 | it 'should go to the previous page' do
65 | @result_set.go_to_page(2).prev_page.should == @result_set.go_to_page(1)
66 | end
67 |
68 | it 'should return nil when there is no previous page' do
69 | @result_set.go_to_page(1).prev_page.should be_nil
70 | end
71 | end
72 |
73 | context 'to_s' do
74 | it 'should return the correct string' do
75 | @result_set.to_s.should == '# ' + @expected_result_size.to_s + '>'
76 | end
77 | end
78 |
79 | context 'check_results' do
80 | it 'should raise an exception if there is an error message' do
81 | stub_request(:get, /http:\/\/isbndb\.com\/api\/books\.xml\?access_key=ABC123&index1=title(&page_number=(.+))?&results=details&value1=hello/).to_return(:body => File.new('spec/responses/access_key_error.xml'), :headers => {'Content-Type'=>'text/xml'})
82 | proc = lambda{ ISBNdb::ResultSet.new('/books.xml?access_key=ABC123&index1=title&results=details&value1=hello', :books) }
83 |
84 | proc.should raise_error(ISBNdb::AccessKeyError)
85 | end
86 | end
87 |
88 | context 'get_total_pages' do
89 | it 'should get the total number of pages' do
90 | @result_set.send(:get_total_pages).should == 167
91 | end
92 | end
93 | end
94 |
95 | describe "MultipleISBNdb::ResultSet" do
96 | before do
97 | stub_request(:get, /http:\/\/isbndb\.com\/api\/books\.xml\?access_key=ABC123&index1=title(&page_number=(.+))?&results=details&value1=hello/).to_return(:body => File.new('spec/responses/books_hello.xml'), :headers => {'Content-Type'=>'text/xml'})
98 | @result_set = ISBNdb::ResultSet.new('/books.xml?access_key=ABC123&index1=title&results=details&value1=hello', :books)
99 | @expected_result_size = 10
100 | @expected_parsed_response = {"server_time"=>"2012-06-16T20:10:13Z", "BookList"=>{"total_results"=>"1664", "page_size"=>"10", "page_number"=>"1", "shown_results"=>"10", "BookData"=>[{"book_id"=>"100th_day_of_school_a04", "isbn"=>"1590543947", "isbn13"=>"9781590543948", "Title"=>"100th Day of School", "TitleLong"=>"100th Day of School (Hello Reader Level 2)", "AuthorsText"=>"Angela Shelf Medearis, ", "PublisherText"=>{"publisher_id"=>"fitzgerald_books", "__content__"=>"Fitzgerald Books"}, "Details"=>{"change_time"=>"2011-03-08T18:28:45Z", "price_time"=>"2012-05-29T16:45:46Z", "edition_info"=>"Unknown Binding; 2007-01", "language"=>"", "physical_description_text"=>"6.0\"x9.0\"x0.5\"; 0.4 lb; 32 pages", "lcc_number"=>"", "dewey_decimal_normalized"=>"", "dewey_decimal"=>""}}, {"book_id"=>"100th_day_the", "isbn"=>"0439330173", "isbn13"=>"9780439330176", "Title"=>"100th Day, The", "TitleLong"=>"100th Day, The (level 1) (Hello Reader Level 1)", "AuthorsText"=>"Alayne Pick, Grace Maccarone, Laura Freeman (Illustrator)", "PublisherText"=>{"publisher_id"=>"cartwheel", "__content__"=>"Cartwheel"}, "Details"=>{"change_time"=>"2006-02-27T21:34:15Z", "price_time"=>"2012-05-29T16:46:08Z", "edition_info"=>"Paperback; 2002-12-01", "language"=>"", "physical_description_text"=>"32 pages", "lcc_number"=>"", "dewey_decimal_normalized"=>"", "dewey_decimal"=>""}}, {"book_id"=>"2011_hello_kitty_engagement_calendar", "isbn"=>"1423803558", "isbn13"=>"9781423803553", "Title"=>"2011 Hello Kitty Engagement Calendar", "TitleLong"=>nil, "AuthorsText"=>"Day Dream (Contributor)", "PublisherText"=>{"publisher_id"=>"day_dream", "__content__"=>"Day Dream"}, "Details"=>{"change_time"=>"2010-09-21T21:20:39Z", "price_time"=>"2012-03-24T04:44:44Z", "edition_info"=>"Calendar; 2010-08-01", "language"=>"", "physical_description_text"=>"7.0\"x8.5\"x0.9\"; 1.1 lb", "lcc_number"=>"", "dewey_decimal_normalized"=>"741", "dewey_decimal"=>"741"}}, {"book_id"=>"2011_hello_kitty_wall_calendar", "isbn"=>"1423803981", "isbn13"=>"9781423803980", "Title"=>"2011 Hello Kitty Wall Calendar", "TitleLong"=>nil, "AuthorsText"=>"Day Dream (Contributor)", "PublisherText"=>{"publisher_id"=>"day_dream", "__content__"=>"Day Dream"}, "Details"=>{"change_time"=>"2010-09-21T18:46:02Z", "price_time"=>"2012-04-25T20:26:40Z", "edition_info"=>"Calendar; 2010-08-01", "language"=>"", "physical_description_text"=>"10.8\"x11.8\"x0.2\"; 0.3 lb", "lcc_number"=>"", "dewey_decimal_normalized"=>"", "dewey_decimal"=>""}}, {"book_id"=>"2012_hello_kitty_2_year_pocket_planner_calendar", "isbn"=>"1423809424", "isbn13"=>"9781423809425", "Title"=>"2012 Hello Kitty 2 Year Pocket Planner Calendar", "TitleLong"=>nil, "AuthorsText"=>"Day Dream, ", "PublisherText"=>{"publisher_id"=>"day_dream", "__content__"=>"Day Dream"}, "Details"=>{"change_time"=>"2012-01-17T22:23:43Z", "price_time"=>"2012-03-04T05:32:03Z", "edition_info"=>"Calendar; 2011-07-01", "language"=>"", "physical_description_text"=>"3.5\"x6.2\"x0.0\"; 0.1 lb", "lcc_number"=>"", "dewey_decimal_normalized"=>"636", "dewey_decimal"=>"636"}}, {"book_id"=>"2012_hello_kitty_juvenile_activity_calendar", "isbn"=>"1423811194", "isbn13"=>"9781423811190", "Title"=>"2012 Hello Kitty Juvenile Activity Calendar", "TitleLong"=>nil, "AuthorsText"=>"Day Dream, ", "PublisherText"=>{"publisher_id"=>"day_dream", "__content__"=>"Day Dream"}, "Details"=>{"change_time"=>"2012-05-15T00:52:54Z", "price_time"=>"2012-06-16T20:10:13Z", "edition_info"=>"Calendar; 2011-07-01", "language"=>"", "physical_description_text"=>"11.0\"x12.0\"x0.3\"; 0.8 lb", "lcc_number"=>"", "dewey_decimal_normalized"=>"741", "dewey_decimal"=>"741"}}, {"book_id"=>"2012_hello_kitty_mini_calendar", "isbn"=>"1423809165", "isbn13"=>"9781423809166", "Title"=>"2012 Hello Kitty Mini Calendar", "TitleLong"=>nil, "AuthorsText"=>"Day Dream, ", "PublisherText"=>{"publisher_id"=>"day_dream", "__content__"=>"Day Dream"}, "Details"=>{"change_time"=>"2012-01-17T22:24:12Z", "price_time"=>"2012-02-09T06:11:01Z", "edition_info"=>"Calendar; 2011-07-01", "language"=>"", "physical_description_text"=>"6.2\"x6.9\"x0.2\"; 0.1 lb", "lcc_number"=>"", "dewey_decimal_normalized"=>"741", "dewey_decimal"=>"741"}}, {"book_id"=>"2012_hello_kitty_wall_calendar", "isbn"=>"1423809696", "isbn13"=>"9781423809692", "Title"=>"2012 Hello Kitty Wall Calendar", "TitleLong"=>nil, "AuthorsText"=>"Day Dream, ", "PublisherText"=>{"publisher_id"=>"day_dream", "__content__"=>"Day Dream"}, "Details"=>{"change_time"=>"2012-01-17T22:14:27Z", "price_time"=>"2012-02-09T06:02:14Z", "edition_info"=>"Calendar; 2011-07-01", "language"=>"", "physical_description_text"=>"10.6\"x11.8\"x0.2\"; 0.5 lb", "lcc_number"=>"", "dewey_decimal_normalized"=>"741", "dewey_decimal"=>"741"}}, {"book_id"=>"2012_hello_kitty_weekly_engagement_calendar", "isbn"=>"1423809092", "isbn13"=>"9781423809098", "Title"=>"2012 Hello Kitty Weekly Engagement Calendar", "TitleLong"=>nil, "AuthorsText"=>"Day Dream, ", "PublisherText"=>{"publisher_id"=>"day_dream", "__content__"=>"Day Dream"}, "Details"=>{"change_time"=>"2012-01-17T22:14:34Z", "price_time"=>"2012-02-08T12:37:05Z", "edition_info"=>"Calendar; 2011-07-01", "language"=>"", "physical_description_text"=>"7.2\"x8.5\"x0.9\"; 1.0 lb", "lcc_number"=>"", "dewey_decimal_normalized"=>"741", "dewey_decimal"=>"741"}}, {"book_id"=>"2_grrrls_hello_gorgeous", "isbn"=>"0439187370", "isbn13"=>"9780439187374", "Title"=>"Hello gorgeous", "TitleLong"=>"Hello gorgeous: a guide to style", "AuthorsText"=>"by Kristen Kemp", "PublisherText"=>{"publisher_id"=>"scholastic", "__content__"=>"New York : Scholastic, c2000."}, "Details"=>{"change_time"=>"2009-09-29T18:09:13Z", "price_time"=>"2011-03-25T22:20:05Z", "edition_info"=>"(pbk.) :$3.99", "language"=>"eng", "physical_description_text"=>"64 p. : ill. (some col.) ; 20 cm.", "lcc_number"=>"", "dewey_decimal_normalized"=>"", "dewey_decimal"=>""}}]}}
101 | end
102 | it_behaves_like "AProperResultSet" do
103 | end
104 | end
105 |
106 |
107 | describe "SingleISBNdb::ResultSet" do
108 | before do
109 | stub_request(:get, /http:\/\/isbndb\.com\/api\/books\.xml\?access_key=ABC123&index1=title(&page_number=(.+))?&results=details&value1=hello/).to_return(:body => File.new('spec/responses/single_book.xml'), :headers => {'Content-Type'=>'text/xml'})
110 | @result_set = ISBNdb::ResultSet.new('/books.xml?access_key=ABC123&index1=title&results=details&value1=hello', :books)
111 | @expected_parsed_response = {"server_time"=>"2012-06-16T20:10:13Z", "BookList"=>{"total_results"=>"1664", "page_size"=>"10", "page_number"=>"1", "shown_results"=>"1", "BookData"=>{"book_id"=>"100th_day_of_school_a04", "isbn"=>"1590543947", "isbn13"=>"9781590543948", "Title"=>"100th Day of School", "TitleLong"=>"100th Day of School (Hello Reader Level 2)", "AuthorsText"=>"Angela Shelf Medearis, ", "PublisherText"=>{"publisher_id"=>"fitzgerald_books", "__content__"=>"Fitzgerald Books"}, "Details"=>{"change_time"=>"2011-03-08T18:28:45Z", "price_time"=>"2012-05-29T16:45:46Z", "edition_info"=>"Unknown Binding; 2007-01", "language"=>"", "physical_description_text"=>"6.0\"x9.0\"x0.5\"; 0.4 lb; 32 pages", "lcc_number"=>"", "dewey_decimal_normalized"=>"", "dewey_decimal"=>""}}}}
112 | @expected_result_size = 1
113 | end
114 | it_behaves_like "AProperResultSet" do
115 | end
116 | end
117 |
118 |
--------------------------------------------------------------------------------
/spec/units/result_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe ISBNdb::Result do
4 | context 'initialize' do
5 | it 'should set the @store instance variable' do
6 | ISBNdb::Result.new.instance_variable_get('@store').should_not be_nil
7 | end
8 | end
9 |
10 | context 'instance_methods' do
11 | context 'when the hash uses symbols for keys' do
12 | it 'should return an array of all methods' do
13 | ISBNdb::Result.new({ :foo => 'bar', :zip => 'zap' }).instance_methods.should == ['foo', 'zip']
14 | end
15 | end
16 |
17 | context 'when the hash is empty' do
18 | it 'should return an empty array' do
19 | ISBNdb::Result.new.instance_methods.should be_empty
20 | end
21 | end
22 | end
23 |
24 | context 'to_s' do
25 | context 'with no instance_methods' do
26 | it 'should return the correct string' do
27 | ISBNdb::Result.new.to_s.should == '#'
28 | end
29 | end
30 |
31 | context 'with instance_methods' do
32 | it 'should return the correct string' do
33 | ISBNdb::Result.new({ :foo => 'bar', :zip => 'zap' }).to_s.should == '#'
34 | end
35 | end
36 | end
37 |
38 | context 'inspect' do
39 | context 'with no instance_methods' do
40 | it 'should return the correct string' do
41 | ISBNdb::Result.new.inspect.should == '#'
42 | end
43 | end
44 |
45 | context 'with instance_methods' do
46 | it 'should return the correct string' do
47 | ISBNdb::Result.new({ :foo => 'bar', :zip => 'zap' }).inspect.should == '# "bar", :zip => "zap">'
48 | end
49 | end
50 | end
51 |
52 | context 'build_result' do
53 | before do
54 | @result = ISBNdb::Result.new
55 | end
56 |
57 | context 'with cAmElCaSeD keys' do
58 | it 'should covert them to underscored keys' do
59 | @result.send(:build_result, { 'FooBar' => 'yep', 'ZipZap' => 'nope' }).should == { 'foo_bar' => 'yep', 'zip_zap' => 'nope' }
60 | end
61 | end
62 |
63 | context 'with under_scored keys' do
64 | it 'should not convert the keys' do
65 | @result.send(:build_result, { 'foo_bar' => 'yep', 'zip_zap' => 'nope' }).should == { 'foo_bar' => 'yep', 'zip_zap' => 'nope' }
66 | end
67 | end
68 |
69 | context 'with symbols for keys' do
70 | it 'should convert the keys to strings' do
71 | @result.send(:build_result, { :foo => 'bar', :zip => 'zap' }).should == { 'foo' => 'bar', 'zip' => 'zap' }
72 | end
73 | end
74 |
75 | context 'with strings for keys' do
76 | it 'not alter the keys' do
77 | @result.send(:build_result, { 'foo' => 'bar', 'zip' => 'zap' }).should == { 'foo' => 'bar', 'zip' => 'zap' }
78 | end
79 | end
80 |
81 | context 'with symbols as strings for keys' do
82 | it 'should convert the keys to strings' do
83 | @result.send(:build_result, { :'foo' => 'bar', :'zip' => 'zap' }).should == { 'foo' => 'bar', 'zip' => 'zap' }
84 | end
85 | end
86 |
87 | context 'with an empty hash' do
88 | it 'should return an empty hash' do
89 | @result.send(:build_result, {}).should be_empty
90 | end
91 | end
92 |
93 | context 'with a flat hash' do
94 | it 'should return the correct hash' do
95 | @result.send(:build_result, { :foo => 'bar', :zip => 'zap' }).should == { 'foo' => 'bar', 'zip' => 'zap' }
96 | end
97 | end
98 |
99 | context 'with a nested hash' do
100 | it 'should convert nested levels' do
101 | @result.send(:build_result, { 'foo' => { :bar => 'zip', 'LeftRight' => 'blue', :'MonkeyPatch' => 'true' } }).should == { 'foo' => { 'bar' => 'zip', 'left_right' => 'blue', 'monkey_patch' => 'true' } }
102 | end
103 | end
104 | end
105 | end
106 |
--------------------------------------------------------------------------------
/spec/units/string_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe String do
4 | context 'is_plural?' do
5 | it 'should return true if the string is plural' do
6 | 'authors'.is_plural?.should be_true
7 | 'books'.is_plural?.should be_true
8 | 'subjects'.is_plural?.should be_true
9 | 'categories'.is_plural?.should be_true
10 | 'publishers'.is_plural?.should be_true
11 | end
12 |
13 | it 'should return false if the string is not plural' do
14 | 'author'.is_plural?.should_not be_true
15 | 'book'.is_plural?.should_not be_true
16 | 'subject'.is_plural?.should_not be_true
17 | 'category'.is_plural?.should_not be_true
18 | 'publisher'.is_plural?.should_not be_true
19 | end
20 | end
21 |
22 | context 'is_singular?' do
23 | it 'should return true if the string is singular' do
24 | 'author'.is_singular?.should be_true
25 | 'book'.is_singular?.should be_true
26 | 'subject'.is_singular?.should be_true
27 | 'category'.is_singular?.should be_true
28 | 'publisher'.is_singular?.should be_true
29 | end
30 |
31 | it 'should return false if the string is not singular' do
32 | 'authors'.is_singular?.should_not be_true
33 | 'books'.is_singular?.should_not be_true
34 | 'subjects'.is_singular?.should_not be_true
35 | 'categories'.is_singular?.should_not be_true
36 | 'publishers'.is_singular?.should_not be_true
37 | end
38 | end
39 |
40 | context 'titleize' do
41 | it 'should capitalize the first letter' do
42 | 'author'.titleize.should == 'Author'
43 | 'book'.titleize.should == 'Book'
44 | 'subject'.titleize.should == 'Subject'
45 | 'category'.titleize.should == 'Category'
46 | 'publisher'.titleize.should == 'Publisher'
47 | end
48 |
49 | it 'should do nothing if the letter is already capitalized' do
50 | 'Author'.titleize.should == 'Author'
51 | 'Book'.titleize.should == 'Book'
52 | 'Subject'.titleize.should == 'Subject'
53 | 'Category'.titleize.should == 'Category'
54 | 'Publisher'.titleize.should == 'Publisher'
55 | end
56 | end
57 |
58 | context 'singularize' do
59 | it 'should return the singular form of a word' do
60 | 'authors'.singularize.should == 'author'
61 | 'books'.singularize.should == 'book'
62 | 'subjects'.singularize.should == 'subject'
63 | 'categories'.singularize.should == 'category'
64 | 'publishers'.singularize.should == 'publisher'
65 | end
66 |
67 | it 'should not alter an already singular word' do
68 | 'author'.singularize.should == 'author'
69 | 'book'.singularize.should == 'book'
70 | 'subject'.singularize.should == 'subject'
71 | 'category'.singularize.should == 'category'
72 | 'publisher'.singularize.should == 'publisher'
73 | end
74 | end
75 |
76 | context 'pluralize' do
77 | it 'should return the plural form of a word' do
78 | 'author'.pluralize.should == 'authors'
79 | 'book'.pluralize.should == 'books'
80 | 'subject'.pluralize.should == 'subjects'
81 | 'category'.pluralize.should == 'categories'
82 | 'publisher'.pluralize.should == 'publishers'
83 | end
84 |
85 | it 'should not alter an already singular word' do
86 | 'authors'.pluralize.should == 'authors'
87 | 'books'.pluralize.should == 'books'
88 | 'subjects'.pluralize.should == 'subjects'
89 | 'categories'.pluralize.should == 'categories'
90 | 'publishers'.pluralize.should == 'publishers'
91 | end
92 | end
93 |
94 | context 'blank?' do
95 | it 'should return true for the empty string' do
96 | ''.blank?.should be_true
97 | ' '.blank?.should be_true
98 | end
99 |
100 | it 'should not return true for non-empty strings' do
101 | 'hello'.blank?.should_not be_true
102 | end
103 | end
104 |
105 | context 'underscore' do
106 | it 'should convert the camel cased word to underscores' do
107 | 'HelloWorld'.underscore.should == 'hello_world'
108 | 'Cookie'.underscore.should == 'cookie'
109 | 'AReallyLongStringThatMightConfuseTheMethod'.underscore.should == 'a_really_long_string_that_might_confuse_the_method'
110 | end
111 |
112 | it 'should not change already underscored words' do
113 | 'hello_world'.underscore.should == 'hello_world'
114 | 'cookie'.underscore.should == 'cookie'
115 | 'a_really_long_string_that_might_confuse_the_method'.underscore.should == 'a_really_long_string_that_might_confuse_the_method'
116 | end
117 | end
118 | end
119 |
--------------------------------------------------------------------------------