nytimes-movies 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,4 @@
1
+ == 0.0.1 2008-11-13
2
+
3
+ * 1 major enhancement:
4
+ * Initial release
@@ -0,0 +1,55 @@
1
+ = nytimes-movies
2
+
3
+ == DESCRIPTION:
4
+
5
+ A gem for accessing the NY Times' Movie Reviews API (http://developer.nytimes.com/)
6
+
7
+ == FEATURES/PROBLEMS:
8
+
9
+ * Still under development. Please email jharris@nytimes.com with bugs. Some methods and classes may
10
+ change until we reach a stable 1.0 version.
11
+
12
+ == SYNOPSIS:
13
+
14
+ include Nytimes::Movies
15
+ Base.api_key = 'YOUR API KEY'
16
+ reviews = Review.find(:dvd => true, :reviewer => 'A. O. Scott')
17
+ ao = Critic.find_by_name('A. O. Scott')
18
+
19
+ == REQUIREMENTS:
20
+
21
+ * The JSON gem
22
+ * An API key for the NY Times' Movies API
23
+
24
+ == INSTALL:
25
+
26
+ * sudo gem install harrisj-nytimes-movies --source=http://gems.github.com/
27
+
28
+ == LICENSE:
29
+
30
+ (The MIT License)
31
+
32
+ Copyright (c) 2008 Jacob Harris
33
+
34
+ Permission is hereby granted, free of charge, to any person obtaining
35
+ a copy of this software and associated documentation files (the
36
+ 'Software'), to deal in the Software without restriction, including
37
+ without limitation the rights to use, copy, modify, merge, publish,
38
+ distribute, sublicense, and/or sell copies of the Software, and to
39
+ permit persons to whom the Software is furnished to do so, subject to
40
+ the following conditions:
41
+
42
+ The above copyright notice and this permission notice shall be
43
+ included in all copies or substantial portions of the Software.
44
+
45
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
46
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
47
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
48
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
49
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
50
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
51
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
52
+
53
+ THIS LICENSE APPLIES ONLY TO THE NYTIMES-MOVIES GEM AND DOES NOT ALTER
54
+ OR ABROGATE THE TERMS OF SERVICE FOR THE MOVIE REVIEWS API PROVIDED BY
55
+ THE NEW YORK TIMES.
@@ -0,0 +1,54 @@
1
+ require 'rake'
2
+
3
+ begin
4
+ require 'jeweler'
5
+ Jeweler::Tasks.new do |s|
6
+ s.name = "nytimes-movies"
7
+ s.summary = "A gem for talking to the New York Times Movie Reviews API"
8
+ s.email = "jharris@nytimes.com"
9
+ s.homepage = "http://github.com/harrisj/nytimes-moves"
10
+ s.description = "A gem for talking to the New York Times Movie Reviews API"
11
+ s.authors = ["Jacob Harris"]
12
+ s.files = FileList["[A-Z]*", "{bin,generators,lib,test}/**/*"]
13
+ s.add_dependency 'json'
14
+ end
15
+ rescue LoadError
16
+ puts "Jeweler, or one of its dependencies, is not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
17
+ end
18
+
19
+
20
+ require 'rake/rdoctask'
21
+ Rake::RDocTask.new do |rdoc|
22
+ rdoc.rdoc_dir = 'rdoc'
23
+ rdoc.title = 'nytimes-articles'
24
+ rdoc.options << '--line-numbers' << '--inline-source'
25
+ rdoc.rdoc_files.include('README*')
26
+ rdoc.rdoc_files.include('lib/**/*.rb')
27
+ end
28
+
29
+ require 'rake/testtask'
30
+ Rake::TestTask.new(:test) do |t|
31
+ t.libs << 'lib' << 'test'
32
+ t.pattern = 'test/**/test_*.rb'
33
+ t.verbose = false
34
+ end
35
+
36
+ begin
37
+ require 'rcov/rcovtask'
38
+ Rcov::RcovTask.new do |t|
39
+ t.libs << 'test'
40
+ t.test_files = FileList['test/**/test_*.rb']
41
+ t.verbose = true
42
+ end
43
+ rescue LoadError
44
+ puts "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
45
+ end
46
+
47
+ begin
48
+ require 'cucumber/rake/task'
49
+ Cucumber::Rake::Task.new(:features)
50
+ rescue LoadError
51
+ puts "Cucumber is not available. In order to run features, you must: sudo gem install cucumber"
52
+ end
53
+
54
+ task :default => :test
@@ -0,0 +1,4 @@
1
+ ---
2
+ :minor: 1
3
+ :patch: 1
4
+ :major: 0
@@ -0,0 +1,4 @@
1
+ $:.unshift(File.dirname(__FILE__)) unless
2
+ $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
3
+
4
+ require 'nytimes/movies'
@@ -0,0 +1,6 @@
1
+ require 'nytimes/movies/base'
2
+ require 'nytimes/movies/link'
3
+ require 'nytimes/movies/multimedia_link'
4
+ require 'nytimes/movies/critic'
5
+ require 'nytimes/movies/result_set'
6
+ require 'nytimes/movies/review'
@@ -0,0 +1,81 @@
1
+ require 'open-uri'
2
+ require 'json'
3
+
4
+ module Nytimes
5
+ module Movies
6
+ class Base
7
+ API_SERVER = 'api.nytimes.com'
8
+ API_VERSION = 'v2'
9
+ API_NAME = 'movies'
10
+ API_BASE = "/svc/#{API_NAME}/#{API_VERSION}"
11
+
12
+ @@api_key = nil
13
+ @@copyright = nil
14
+
15
+ class << self
16
+
17
+ ##
18
+ # The copyright footer to be placed at the bottom of any data from the New York Times. Note this is only set after an API call.
19
+ def copyright
20
+ @@copyright
21
+ end
22
+
23
+ ##
24
+ # Set the API key used for operations. This needs to be called before any requests against the API. To obtain an API key, go to http://developer.nytimes.com/
25
+ def api_key=(key)
26
+ @@api_key = key
27
+ end
28
+
29
+ def api_key
30
+ @@api_key
31
+ end
32
+
33
+ ##
34
+ # Builds a request URI to call the API server
35
+ def build_request_url(path, params)
36
+ URI::HTTP.build :host => API_SERVER,
37
+ :path => "#{API_BASE}/#{path}",
38
+ :query => params.map {|k,v| "#{k}=#{v}"}.join('&')
39
+ end
40
+
41
+ def invoke(path, params={})
42
+ begin
43
+ if @@api_key.nil?
44
+ raise "You must initialize the API key before you run any API queries"
45
+ end
46
+
47
+ full_params = params.merge 'api-key' => @@api_key
48
+
49
+ uri = build_request_url(path, full_params)
50
+
51
+ # puts "Request [#{uri}]"
52
+
53
+ reply = uri.read
54
+ parsed_reply = JSON.parse reply
55
+
56
+ if parsed_reply.nil?
57
+ # FIXME
58
+ raise "Empty reply returned from API"
59
+ end
60
+
61
+ #case parsed_reply['status']
62
+ # FIXME
63
+ #end
64
+
65
+ @@copyright = parsed_reply['copyright']
66
+
67
+ parsed_reply
68
+ rescue OpenURI::HTTPError => e
69
+ if e.message =~ /^404/
70
+ return nil
71
+ end
72
+
73
+ raise "Error connecting to URL #{uri} #{e}"
74
+ #rescue JSON::ParserError => e
75
+ # raise RuntimeError, "Invalid JSON returned from CRNR:\n#{reply}"
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,73 @@
1
+ module Nytimes
2
+ module Movies
3
+ class Critic < Base
4
+ attr_reader :display_name, :sort_name, :status, :bio, :seo_name, :photo
5
+
6
+ def initialize(params={})
7
+ params.each_pair do |k,v|
8
+ instance_variable_set("@#{k}", v)
9
+ end
10
+ end
11
+
12
+ ##
13
+ # Returns reviews made by the critic. Please refer to Review#find for other optional flags that can be applied.
14
+ def reviews(params={})
15
+ Review.find(params.merge :reviewer => seo_name)
16
+ end
17
+
18
+ ##
19
+ # Create a Critic object from a hash snippet returned from the API. You should never need to call this.
20
+ def self.create_from_api(params={})
21
+ self.new :display_name => params['display_name'],
22
+ :sort_name => params['sort_name'],
23
+ :status => params['status'],
24
+ :bio => params['bio'],
25
+ :seo_name => params['seo_name'],
26
+ :photo => MultimediaLink.create_from_api(params['multimedia'])
27
+ end
28
+
29
+ ##
30
+ # Returns a list of critics that match a given type. The following types are supported:
31
+ # * <tt>:full_time</tt> - fulltime critics at the New York Times
32
+ # * <tt>:part_time</tt> - part-time critics at the New York Times
33
+ # * <tt>:all</tt> - all critics
34
+ def self.find_by_type(type)
35
+ key = case type
36
+ when :full_time
37
+ 'full-time'
38
+ when :part_time
39
+ 'part-time'
40
+ when :all
41
+ 'all'
42
+ else
43
+ raise ArgumentError, "Type can be :full_time, :part_time, or :all"
44
+ end
45
+
46
+ reply = invoke("critics/#{key}")
47
+ results = reply['results']
48
+ results.map {|r| self.create_from_api(r)}
49
+ end
50
+
51
+ ##
52
+ # Escapes a critic's name to the SEO variation used for name searches. This is automatically used by Critic#find_by_name, so you don't need to call it.
53
+ def self.escape_critic_name(name)
54
+ return name if name =~ /^[a-z\-]+$/ # might be escaped already
55
+ name.downcase.gsub(/[^a-z\s]/, ' ').gsub(/\s+/, '-')
56
+ end
57
+
58
+ ##
59
+ # Find a critic record matching a given name. Both the English name (eg, 'A. O. Scott') and the SEO name ('a-o-scott') are valid keys, although the SEO name is
60
+ # suggested if you want to avoid ambiguity. Only exact matches are used (no last name searches). If a single matching record is found, returns a single Critic instance.
61
+ # In the admittedly rare chance of 2 distinct critics having the same name, returns an array. Returns nil if no matches are found.
62
+ def self.find_by_name(name)
63
+ name = escape_critic_name(name)
64
+ reply = invoke("critics/#{name}")
65
+ return nil if reply.nil? || reply['results'].nil?
66
+ results = reply['results']
67
+ critics = results.map {|r| self.create_from_api(r)}
68
+ return critics.first if critics.length == 1
69
+ critics
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,24 @@
1
+ module Nytimes
2
+ module Movies
3
+
4
+ ##
5
+ # Represents a link returned from the Reviews API. Each link has a URL, suggested text, and a link_type which gives you some idea of what the remote
6
+ # target of the link is. Possible link types include:
7
+ # FIXME
8
+ class Link
9
+ attr_reader :link_type, :url, :suggested_text
10
+
11
+ def initialize(hash={})
12
+ @url = hash[:url]
13
+ @link_type = hash[:link_type]
14
+ @suggested_text = hash[:suggested_text]
15
+ end
16
+
17
+ ##
18
+ # Create a Link object from a hash snippet returned from the API. You should never need to call this.
19
+ def self.create_from_api(hash={})
20
+ Link.new :url => hash['url'], :link_type => hash['type'], :suggested_text => hash['suggested_link_text']
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,30 @@
1
+ module Nytimes
2
+ module Movies
3
+
4
+ ##
5
+ # Represents a link to a multimedia asset from the New York Times. Invariably these are images or thumbnails, but it could also be expanded to
6
+ # include movies or sound. The following attributes are part of the link. FIXME
7
+ class MultimediaLink
8
+ attr_reader :media_type, :url, :height, :width, :credit
9
+ private_class_method :new
10
+
11
+ def initialize(hash={})
12
+ hash.each_pair do |k,v|
13
+ instance_variable_set("@#{k}", v)
14
+ end
15
+ end
16
+
17
+ ##
18
+ # Create a MultimediaLink object from a hash snippet returned from the API. You should never need to call this.
19
+ def self.create_from_api(hash={})
20
+ return nil if hash.nil? || hash.empty?
21
+
22
+ if hash.key? 'resource'
23
+ hash = hash['resource']
24
+ end
25
+
26
+ new(:media_type => hash['type'], :url => hash['src'], :height => hash['height'], :width => hash['width'], :credit => hash['credit'])
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,46 @@
1
+ module Nytimes
2
+ module Movies
3
+ class ResultSet
4
+ attr_reader :num_results, :offset, :results
5
+
6
+ def initialize(params, json_reply, coerce_type)
7
+ @num_results = json_reply['num_results']
8
+ @params = params.dup
9
+ @offset = @params['offset'] || 0
10
+ @results = json_reply['results'].map {|r| coerce_type.create_from_api(r)}
11
+ end
12
+
13
+ ##
14
+ # The first display index of the result set. Note this is a human index, not a programmer index; it starts from 1
15
+ def first_index
16
+ offset + 1
17
+ end
18
+
19
+ ##
20
+ # The last display index of the result set.
21
+ def last_index
22
+ first_index + batch_size - 1
23
+ end
24
+
25
+ ##
26
+ # The page of this result set
27
+ def page_number
28
+ offset / batch_size + 1
29
+ end
30
+
31
+ ##
32
+ # The calculated number of pages returned from the movies API.
33
+ def num_pages
34
+ (num_results.to_f / batch_size).ceil
35
+ end
36
+
37
+ ##
38
+ # The number of results returned in a result set
39
+ def batch_size
40
+ Review::BATCH_SIZE
41
+ end
42
+
43
+ alias per_page batch_size
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,171 @@
1
+ module Nytimes
2
+ module Movies
3
+ class Review < Base
4
+ attr_reader :nyt_movie_id, :display_title, :sort_name, :mpaa_rating, :byline, :headline, :summary_short, :publication_date, :opening_date, :dvd_release_date, \
5
+ :date_updated, :seo_name, :critics_pick, :thousand_best, :thumbnail, :link, :related_links, :critic
6
+
7
+ alias :updated_on :date_updated
8
+ alias :critics_pick? :critics_pick
9
+ alias :thousand_best? :thousand_best
10
+
11
+ alias :critic_name :byline
12
+ alias :movie_title :display_title
13
+ alias :review_title :headline
14
+
15
+ BATCH_SIZE = 20
16
+
17
+ def initialize(params={})
18
+ params.each_pair do |k,v|
19
+ instance_variable_set("@#{k}", v)
20
+ end
21
+ end
22
+
23
+ class <<self
24
+ def parse_date(value)
25
+ return nil if value.nil?
26
+ Date.parse(value)
27
+ end
28
+
29
+ def rename_hash_key(hash, key, new_key)
30
+ hash[new_key] = hash.delete(key)
31
+ end
32
+
33
+ def coerce_key_boolean(hash, key)
34
+ hash[key] = hash[key] == 'Y'
35
+ end
36
+
37
+ def format_date_arg(arg)
38
+ return arg if arg.is_a? String
39
+
40
+ unless arg.respond_to? :strftime
41
+ raise "Date argument must respond to strftime"
42
+ end
43
+
44
+ arg.strftime "%Y-%m-%d"
45
+ end
46
+
47
+ def date_to_query_arg(date_or_range)
48
+ return nil if date_or_range.nil?
49
+ if date_or_range.respond_to?(:first) && date_or_range.respond_to?(:last)
50
+ "#{format_date_arg(date_or_range.first)};#{format_date_arg(date_or_range.last)}"
51
+ else
52
+ format_date_arg(date_or_range)
53
+ end
54
+ end
55
+
56
+ def boolean_to_query_arg(arg)
57
+ return nil if arg.nil?
58
+ arg ? 'Y' : 'N'
59
+ end
60
+
61
+ def create_from_api(hash)
62
+ hash = hash.dup
63
+
64
+ hash['dvd_release_date'] = parse_date(hash['dvd_release_date'])
65
+ hash['opening_date'] = parse_date(hash['opening_date'])
66
+ hash['publication_date'] = parse_date(hash['publication_date'])
67
+ hash['date_updated'] = parse_date(hash['date_updated'])
68
+
69
+ coerce_key_boolean hash, 'critics_pick'
70
+ coerce_key_boolean hash, 'thousand_best'
71
+
72
+ multimedia = hash.delete('multimedia')
73
+ unless multimedia.nil?
74
+ hash['thumbnail'] = MultimediaLink.create_from_api(multimedia)
75
+ end
76
+
77
+ if hash.has_key? 'link'
78
+ hash['link'] = Link.create_from_api(hash['link'])
79
+ end
80
+
81
+ related = hash.delete('related_urls')
82
+ unless related.nil?
83
+ hash['related_links'] = related.map {|l| Link.create_from_api(l) }
84
+ end
85
+
86
+ rename_hash_key hash, 'seo-name', 'seo_name'
87
+ new hash
88
+ end
89
+
90
+ ##
91
+ # Find Movie Reviews that match your parameters. Up to 3 of the following query parameters can be used in a request:
92
+ #
93
+ # * <tt>:text</tt> - search movie title and indexed terms for the movie. To limit to exact matches, enclose parts of the query in single quotes. Otherwise, it will include include partial matches ("head words") as well as exact matches (e.g., president will match president, presidents and presidential). Multiple terms will be ORed together
94
+ # * <tt>:critics_pick</tt> - set to true to only search movies that are designated critics picks. Should your tastes be more lowbrow, set to false to return movies that are specifically NOT critic's picks. To get all movies, don't send this parameter at all.
95
+ # * <tt>:thousand_best</tt> - search only movies on the Times List of "Thousand Best Movies Ever Made". Set to false if you want to explicitly search movies not on the list. Omit if you want all movies.
96
+ # * <tt>:dvd</tt> - if true, search only movies out on DVD. If false, movies not on DVD. Don't specify if you want all movies.
97
+ # * <tt>:reviewer</tt> - search reviews made by a specific critic. The full-name or the SEO name can be used to specify the critic.
98
+ # * <tt>:publication_date</tt> - when the review was published. This can be either a single Date or Time object or a range of Times (anything that responds to .first and .last). If you'd prefer, you can also pass in a "YYYY-MM-DD" string
99
+ # * <tt>:opening_date</tt> - when the movie opened in theaters in the NY Metro area. See +:publication_date+ for argument times.
100
+ #
101
+ # In addition, you can also specify the following order and pagination values:
102
+ # * <tt>:offset</tt> - a maximum of 20 result are returned for queries. To retrieve further pages, an offset from the first result can be specified. This must be a multiple of 20. So +20+ means results +21 - 40+
103
+ # * <tt>:page</tt> - as a convenience, page will calculate the offset for here. This is not an API parameter and is translated into an offset.
104
+ # * <tt>:order</tt> - ordering for the results. The following four sorting options are available: <tt>:title</tt> (<em>ascending</em>), <tt>:publication_date</tt>, <tt>:opening_date</tt>, <tt>:dvd_release_date</tt> (<em>most recent first</em>). If you do not specify a sort order, the results will be ordered by closest match.
105
+ # * <tt>:load_critics</tt> - if true, automatically load the Critic object for each record's reviewer. Note that this requires N additional API calls (where N is the unique number of critics in the result set)
106
+ def find(in_params={})
107
+ params = {}
108
+
109
+ if in_params[:text]
110
+ params['query'] = in_params[:text]
111
+ end
112
+
113
+ params['thousand-best'] = boolean_to_query_arg(in_params[:thousand_best])
114
+ params['critics-pick'] = boolean_to_query_arg(in_params[:critics_pick])
115
+ params['dvd'] = boolean_to_query_arg(in_params[:dvd])
116
+
117
+ params['publication-date'] = date_to_query_arg(in_params[:publication_date])
118
+ params['opening-date'] = date_to_query_arg(in_params[:opening_date])
119
+
120
+ if in_params.has_key? :reviewer
121
+ params['reviewer'] = Critic.escape_critic_name(in_params[:reviewer])
122
+ end
123
+
124
+ params['offset'] = in_params[:offset]
125
+
126
+ if in_params.has_key? :page
127
+ params['offset'] = BATCH_SIZE * (in_params[:page] - 1)
128
+ end
129
+
130
+ if in_params.has_key? :order
131
+ params['order'] = case in_params[:order]
132
+ when :title, :publication_date, :opening_date, :dvd_release_date
133
+ "by-#{in_params[:order].to_s.gsub('_', '-')}"
134
+ else
135
+ raise ArgumentError, "Order can only be :title, :publication_date, :opening_date, or :dvd_release_date"
136
+ end
137
+ end
138
+
139
+ params.delete_if {|k, v| v.nil? }
140
+
141
+ reply = invoke 'reviews/search', params
142
+ out = ResultSet.new(params, reply, Review)
143
+
144
+ if in_params[:load_critics]
145
+ critics_hash = {}
146
+ out.results.each do |r|
147
+ name = r.byline
148
+
149
+ if critics_hash.has_key? name
150
+ critic = critics_hash[name]
151
+ else
152
+ critic = Critic.find_by_name(name)
153
+ critics_hash[name] = critic
154
+ end
155
+
156
+ r.instance_variable_set '@critic', critic
157
+ end
158
+ end
159
+
160
+ out
161
+ end
162
+
163
+ def find_by_type(params={})
164
+ end
165
+
166
+ def find_by_reviewer(params={})
167
+ end
168
+ end
169
+ end
170
+ end
171
+ end
@@ -0,0 +1,105 @@
1
+ require File.dirname(__FILE__) + '/../../test_helper.rb'
2
+
3
+ CRITIC_HASH = {"display_name" => "A. O. Scott",
4
+ "sort_name" => "A. O. Scott",
5
+ "status" => "full-time",
6
+ "bio" => "A. O. Scott joined The New York Times as a film critic in January 2000. Previously, Mr. Scott was a Sunday book reviewer for Newsday and a frequent contributor to Slate, The New York Review of Books, and many other publications. He has served on the editorial staffs of Lingua Franca and The New York Review of Books. He also edited \"A Bolt from the Blue and Other Essays,\" a collection by Mary McCarthy, which was published by The New York Review of Books in 2002. In addition to his film-reviewing duties, Mr. Scott often writes for the Times Magazine and the Book Review. He was born on July 10, 1966, in Northampton, Mass., and now lives in Brooklyn, N.Y. with his wife and two children.",
7
+ "seo_name" => "A-O-Scott",
8
+ "multimedia" => {"resource" => {"type" => "image","src" => "http:\/\/graphics8.nytimes.com\/images\/2007\/03\/02\/movies\/scott.163.jpg", "height" => nil,"width" => nil,"credit" => "Tony Cenicola\/<br>The New York Times"}}}
9
+
10
+ FIND_BY_NAME_REPLY = <<-EOF
11
+ {"status":"OK","copyright":"Copyright (c) 2008 The New York Times Company. All Rights Reserved.","results":[{"display_name":"A. O. Scott","sort_name":"A. O. Scott","status":"full-time","bio":"A. O. Scott joined The New York Times as a film critic in January 2000. Previously, Mr. Scott was a Sunday book reviewer for Newsday and a frequent contributor to Slate, The New York Review of Books, and many other publications. He has served on the editorial staffs of Lingua Franca and The New York Review of Books. He also edited \\\"A Bolt from the Blue and Other Essays,\\\" a collection by Mary McCarthy, which was published by The New York Review of Books in 2002. In addition to his film-reviewing duties, Mr. Scott often writes for the Times Magazine and the Book Review. He was born on July 10, 1966, in Northampton, Mass., and now lives in Brooklyn, N.Y. with his wife and two children.","seo_name":"A-O-Scott","multimedia":{"resource":{"type":"image","src":"http://graphics8.nytimes.com/images/2007/03/02/movies/scott.163.jpg","height":null,"width":null,"credit":"Tony Cenicola/<br>The New York Times"}}}]}
12
+ EOF
13
+
14
+ class TestNytimes::TestMovies::TestCritic < Test::Unit::TestCase
15
+ include Nytimes::Movies
16
+
17
+ # global setup
18
+ def setup
19
+ FakeWeb.clean_registry
20
+ FakeWeb.block_uri_pattern(Base::API_SERVER)
21
+ end
22
+
23
+ context "Critic.create_from_api" do
24
+ setup do
25
+ @critic = Critic.create_from_api(CRITIC_HASH)
26
+ end
27
+
28
+ should "return an object of the Critic type" do
29
+ assert_kind_of(Critic, @critic)
30
+ end
31
+
32
+ %w(display_name sort_name status bio).each do |attr|
33
+ should "assign the value of the @#{attr} attribute from the '#{attr}' key in the hash" do
34
+ assert_equal(CRITIC_HASH[attr], @critic.send(attr))
35
+ end
36
+ end
37
+
38
+ context "for the multimedia hash value" do
39
+ should "assign it to the @photo attribute" do
40
+ assert_not_nil @critic.photo
41
+ end
42
+
43
+ should "return a Nytimes::Movies::MultimediaLink instance" do
44
+ assert_kind_of(MultimediaLink, @critic.photo)
45
+ end
46
+ end
47
+ end
48
+
49
+ context "Critic.escape_critic_name" do
50
+ should "not escape a name that looks like it's escaped" do
51
+ assert_equal 'a-o-scott', Critic.escape_critic_name('a-o-scott')
52
+ end
53
+
54
+ should "downcase a name and replace spaces with hyphens" do
55
+ assert_equal 'mahnola-dargis', Critic.escape_critic_name('Mahnola Dargis')
56
+ end
57
+
58
+ should "not include punctuation characters in the escaped name" do
59
+ assert_equal 'a-o-scott', Critic.escape_critic_name('A.O. Scott')
60
+ end
61
+ end
62
+
63
+ context "Critic.find_by_name" do
64
+ context "for a valid critic" do
65
+ setup do
66
+ FakeWeb.register_uri(api_url_for('critics/a-o-scott'), :string => FIND_BY_NAME_REPLY)
67
+ @critic = Critic.find_by_name('a-o-scott')
68
+ end
69
+
70
+ should "return a single Nytimes::Movies::Critic instance" do
71
+ assert_kind_of(Critic, @critic)
72
+ assert_equal 'A. O. Scott', @critic.display_name
73
+ end
74
+ end
75
+
76
+ context "for a nonexistant critic" do
77
+ setup do
78
+ FakeWeb.register_uri(api_url_for('critics/unknown-person'), :string => FIND_BY_NAME_REPLY, :status => [ 404, "Not Found" ])
79
+ @critic = Critic.find_by_name('Unknown Person')
80
+ end
81
+
82
+ should "return nil" do
83
+ assert_nil @critic
84
+ end
85
+ end
86
+ end
87
+
88
+ context "Critic.find_by_type" do
89
+ setup do
90
+ FakeWeb.register_uri(api_url_for('critics/full-time'), :string => FIND_BY_NAME_REPLY)
91
+ end
92
+
93
+ should "raise an ArgumentError if passed anything besides :full_time, :part_time, or :all" do
94
+ assert_raise(ArgumentError) { Critic.find_by_type(:foo) }
95
+ end
96
+
97
+ should "return an array of Nytimes::Movies::Critic instances" do
98
+ @critics = Critic.find_by_type(:full_time)
99
+
100
+ assert_kind_of(Array, @critics)
101
+ assert @critics.all? {|c| c.is_a? Critic}
102
+ assert_equal 'A. O. Scott', @critics.first.display_name
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,29 @@
1
+ require File.dirname(__FILE__) + '/../../test_helper.rb'
2
+
3
+ class TestNytimes::TestMovies::TestLink < Test::Unit::TestCase
4
+ context "Link.create" do
5
+ setup do
6
+ @url = 'http://foo.com'
7
+ @type = 'article'
8
+ @suggest_text = 'Suggested Text'
9
+
10
+ @link = Nytimes::Movies::Link.create_from_api('url' => @url, 'type' => @type, 'suggested_link_text' => @suggest_text)
11
+ end
12
+
13
+ should "return an object of type Nytimes::Movies::Link" do
14
+ assert_kind_of(Nytimes::Movies::Link, @link)
15
+ end
16
+
17
+ should "assign the @url attribute from the 'url' key in the hash" do
18
+ assert_equal(@url, @link.url)
19
+ end
20
+
21
+ should "assign the @link_type attribute from the 'type' key in the hash" do
22
+ assert_equal(@type, @link.link_type)
23
+ end
24
+
25
+ should "assign the @suggested_text attribute from the 'suggested_link_text' key in the hash" do
26
+ assert_equal(@suggest_text, @link.suggested_text)
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,36 @@
1
+ require File.dirname(__FILE__) + '/../../test_helper.rb'
2
+
3
+ LINK_HASH = {"resource" => {"type" => "image","src" => "http:\/\/graphics8.nytimes.com\/images\/2007\/03\/02\/movies\/scott.163.jpg", "height" => 234, "width" => 345,"credit" => "Tony Cenicola\/<br>The New York Times"}}.freeze
4
+
5
+
6
+ class TestNytimes::TestMovies::TestMultimediaLink < Test::Unit::TestCase
7
+ context "MultimediaLink.create_from_api" do
8
+ setup do
9
+ @link = Nytimes::Movies::MultimediaLink.create_from_api(LINK_HASH)
10
+ end
11
+
12
+ should "return an instance of MultimediaLink" do
13
+ assert_kind_of(Nytimes::Movies::MultimediaLink, @link)
14
+ end
15
+
16
+ should "assign the @media_type attribute from the 'type' key in the hash" do
17
+ assert_equal(LINK_HASH['resource']['type'], @link.media_type)
18
+ end
19
+
20
+ should "assign the @url attribute from the 'src' key in the hash" do
21
+ assert_equal(LINK_HASH['resource']['src'], @link.url)
22
+ end
23
+
24
+ should "assign the @height attribute from the 'height' key in the hash" do
25
+ assert_equal(LINK_HASH['resource']['height'], @link.height)
26
+ end
27
+
28
+ should "assign the @width attribute from the 'width' key in the hash" do
29
+ assert_equal(LINK_HASH['resource']['width'], @link.width)
30
+ end
31
+
32
+ should "assign the @credit attribute from the 'credit' key in the hash" do
33
+ assert_equal(LINK_HASH['resource']['credit'], @link.credit)
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,48 @@
1
+ require File.dirname(__FILE__) + '/../../test_helper.rb'
2
+
3
+ class TestNytimes::TestMovies::TestResultSet < Test::Unit::TestCase
4
+ include Nytimes::Movies
5
+
6
+ def setup
7
+ @reply = MOVIE_RESULT_HASH
8
+ @params = {'offset' => 40}
9
+ @result_set = ResultSet.new(@params, @reply, Review)
10
+ end
11
+
12
+ context "first_index" do
13
+ should "equal the offset + 1" do
14
+ assert_equal 41, @result_set.first_index
15
+ end
16
+ end
17
+
18
+ context "last_index" do
19
+ should "equal the first_index + batch_size - 1" do
20
+ assert_equal 60, @result_set.last_index
21
+ end
22
+ end
23
+
24
+ context "num_pages" do
25
+ should "equal the ceiling of num_results / batch_size" do
26
+ @result_set.instance_variable_set '@num_results', 123
27
+
28
+ assert_equal 7, @result_set.num_pages
29
+ end
30
+
31
+ should "equal 1 if the number of results < batch_size" do
32
+ @result_set.instance_variable_set '@num_results', 1
33
+ assert_equal 1, @result_set.num_pages
34
+ end
35
+
36
+ should "not erroneously round up if the num_results % batch_size = 0" do
37
+ @result_set.instance_variable_set '@num_results', 120
38
+ assert_equal 6, @result_set.num_pages
39
+ end
40
+ end
41
+
42
+ context "page_number" do
43
+ should "equal the offset / batch_size + 1" do
44
+ assert_equal 3, @result_set.page_number
45
+ end
46
+ end
47
+
48
+ end
@@ -0,0 +1,198 @@
1
+ require File.dirname(__FILE__) + '/../../test_helper.rb'
2
+
3
+ class TestNytimes::TestMovies::TestReview < Test::Unit::TestCase
4
+ include Nytimes::Movies
5
+
6
+ # global setup
7
+ def setup
8
+ FakeWeb.clean_registry
9
+ FakeWeb.block_uri_pattern(Base::API_SERVER)
10
+ end
11
+
12
+ def expects_invoke_arg(key, value)
13
+ Review.expects(:invoke).with(anything, has_entry(key, value)).returns(MOVIE_RESULT_HASH)
14
+ end
15
+
16
+ context "Review.create_from_api" do
17
+ setup do
18
+ @review = Review.create_from_api(MOVIE_REVIEW_HASH)
19
+ end
20
+
21
+ should "rename seo-name to seo_name" do
22
+ assert_equal MOVIE_REVIEW_HASH['seo-name'], @review.seo_name
23
+ end
24
+
25
+ should "return a MultimediaLink object for thumbnail" do
26
+ assert_kind_of MultimediaLink, @review.thumbnail
27
+ end
28
+
29
+ context "link" do
30
+ should "return a Link object" do
31
+ assert_kind_of Link, @review.link
32
+ end
33
+
34
+ should "map the URL" do
35
+ assert_equal MOVIE_REVIEW_HASH['link']['url'], @review.link.url
36
+ end
37
+ end
38
+
39
+ context "related links" do
40
+ should "return an array of Link objects" do
41
+ assert_kind_of Array, @review.related_links
42
+ @review.related_links.all? {|l| assert_kind_of(Link, l)}
43
+ end
44
+
45
+ should "be one for each link object" do
46
+ assert_equal MOVIE_REVIEW_HASH["related_urls"].length, @review.related_links.length
47
+ end
48
+ end
49
+
50
+ should "map the critics_pick to a boolean" do
51
+ assert_equal true, @review.critics_pick?
52
+ end
53
+
54
+ should "map the thousand_best to a boolean" do
55
+ assert_equal false, @review.thousand_best?
56
+ end
57
+
58
+ %w(nyt_movie_id display_title sort_name mpaa_rating byline headline summary_short).each do |field|
59
+ should "copy the #{field} from the hash into a class attribute" do
60
+ assert_equal MOVIE_REVIEW_HASH[field], @review.send(field)
61
+ end
62
+ end
63
+
64
+ %w(dvd_release_date opening_date date_updated publication_date).each do |date|
65
+ should "parse the #{date} from the hash into a Ruby Date" do
66
+ assert_kind_of Date, @review.send(date)
67
+ assert_equal Date.parse(MOVIE_REVIEW_HASH[date]), @review.send(date)
68
+ end
69
+ end
70
+ end
71
+
72
+ context "Review.find" do
73
+ setup do
74
+ FakeWeb.register_uri(api_url_for('reviews/search', 'query' => 'constantine'), :string => MOVIE_RESULT_REPLY)
75
+ end
76
+
77
+ should "call the reviews/search endpoint"
78
+
79
+ should "return an instance of ResultSet" do
80
+ result_set = Review.find(:text => 'constantine')
81
+ assert_kind_of(ResultSet, result_set)
82
+ end
83
+
84
+ context "input parameters" do
85
+ %w(critics_pick dvd thousand_best).each do |flag|
86
+ context flag do
87
+ should "send Y if the #{flag} is true" do
88
+ expects_invoke_arg(flag.gsub('_','-'), 'Y')
89
+ Review.find(flag.to_sym => true)
90
+ end
91
+
92
+ should "send N if the #{flag} is false" do
93
+ expects_invoke_arg(flag.gsub('_','-'), 'N')
94
+ Review.find(flag.to_sym => false)
95
+ end
96
+ end
97
+ end
98
+
99
+ %w(opening_date publication_date).each do |field|
100
+ context field do
101
+ should "accept a single Date object" do
102
+ d = Date.parse('1/1/2008')
103
+ expects_invoke_arg(field.gsub('_','-'), d.strftime('%Y-%m-%d'))
104
+ Review.find(field.to_sym => d)
105
+ end
106
+
107
+ should "accept a single Time object" do
108
+ t = Time.parse('1/1/2008')
109
+ expects_invoke_arg(field.gsub('_','-'), t.strftime('%Y-%m-%d'))
110
+ Review.find(field.to_sym => t)
111
+ end
112
+
113
+ should "accept a range of Date objects" do
114
+ d1 = Date.parse('1/1/2008')
115
+ d2 = Date.parse('1/30/2008')
116
+ expects_invoke_arg(field.gsub('_','-'), "#{d1.strftime('%Y-%m-%d')};#{d2.strftime('%Y-%m-%d')}")
117
+ Review.find(field.to_sym => d1..d2)
118
+ end
119
+
120
+ should "accept a range of Time objects" do
121
+ t1 = Time.parse('1/1/2008')
122
+ t2 = Time.parse('1/30/2008')
123
+ expects_invoke_arg(field.gsub('_','-'), "#{t1.strftime('%Y-%m-%d')};#{t2.strftime('%Y-%m-%d')}")
124
+ Review.find(field.to_sym => t1..t2)
125
+ end
126
+
127
+ should "accept an array of Date objects" do
128
+ d1 = Date.parse('1/1/2008')
129
+ d2 = Date.parse('1/30/2008')
130
+ expects_invoke_arg(field.gsub('_','-'), "#{d1.strftime('%Y-%m-%d')};#{d2.strftime('%Y-%m-%d')}")
131
+ Review.find(field.to_sym => [d1,d2])
132
+ end
133
+
134
+ should "accept an array of Time objects" do
135
+ t1 = Time.parse('1/1/2008')
136
+ t2 = Time.parse('1/30/2008')
137
+ expects_invoke_arg(field.gsub('_','-'), "#{t1.strftime('%Y-%m-%d')};#{t2.strftime('%Y-%m-%d')}")
138
+ Review.find(field.to_sym => [t1,t2])
139
+ end
140
+ end
141
+ end
142
+
143
+ context "offset" do
144
+ should "send the offset argument through" do
145
+ expects_invoke_arg('offset', 60)
146
+ Review.find(:offset => 60)
147
+ end
148
+ end
149
+
150
+ context "page" do
151
+ should "set the offset to be batchsize * page-1" do
152
+ expects_invoke_arg('offset', 40)
153
+ Review.find(:page => 3)
154
+ end
155
+ end
156
+
157
+ context "ordering parameters" do
158
+ should "send a by-title to the API for :title order" do
159
+ expects_invoke_arg('order', 'by-title')
160
+ Review.find(:order => :title)
161
+ end
162
+
163
+ should "send a by-publication-date to the API for :publication_date order" do
164
+ expects_invoke_arg('order', 'by-publication-date')
165
+ Review.find(:order => :publication_date)
166
+ end
167
+
168
+ should "send a by-opening-date to the API for :opening_date order" do
169
+ expects_invoke_arg('order', 'by-opening-date')
170
+ Review.find(:order => :opening_date)
171
+ end
172
+
173
+ should "send a by-dvd-release-date to the API for :dvd_release_date order" do
174
+ expects_invoke_arg('order', 'by-dvd-release-date')
175
+ Review.find(:order => :dvd_release_date)
176
+ end
177
+
178
+ should "raise an ArgumentError if the ordering is not one of those allowed" do
179
+ assert_raise(ArgumentError) do
180
+ Review.find(:order => :random)
181
+ end
182
+ end
183
+ end
184
+
185
+ context "reviewer" do
186
+ should "escape the Critic Name to the SEO form" do
187
+ expects_invoke_arg('reviewer', 'a-o-scott')
188
+ Review.find(:reviewer => 'A. O. Scott')
189
+ end
190
+
191
+ should "not escape again if already in SEO form" do
192
+ expects_invoke_arg('reviewer', 'a-o-scott')
193
+ Review.find(:reviewer => 'A. O. Scott')
194
+ end
195
+ end
196
+ end
197
+ end
198
+ end
@@ -0,0 +1,46 @@
1
+ require 'test/unit'
2
+ require 'rubygems'
3
+ gem 'thoughtbot-shoulda'
4
+ require 'shoulda'
5
+ gem 'FakeWeb'
6
+ require 'fake_web'
7
+ require 'mocha'
8
+ require 'json'
9
+
10
+ require File.dirname(__FILE__) + '/../lib/nytimes/movies'
11
+
12
+ API_KEY = '13e234323232222'
13
+ Nytimes::Movies::Base.api_key = API_KEY
14
+
15
+ def api_url_for(path, params = {})
16
+ full_params = params.merge 'api-key' => API_KEY
17
+ Nytimes::Movies::Base.build_request_url(path, full_params).to_s
18
+ end
19
+
20
+ module TestNytimes
21
+ module TestMovies
22
+ end
23
+ end
24
+
25
+ MOVIE_REVIEW_HASH = {"dvd_release_date"=>"2008-09-16", "opening_date"=>"2008-04-18",
26
+ "multimedia"=>{"resource"=>{"src"=>"http://graphics8.nytimes.com/images/2008/04/18/arts/18sword-75.jpg", "type"=>"thumbnail", "height"=>75, "width"=>75}},
27
+ "byline"=>"Stephen Holden",
28
+ "nyt_movie_id"=>405736,
29
+ "publication_date"=>"2008-04-18", "critics_pick"=>"Y", "sort_name"=>"Constantine's Sword", "headline"=>"",
30
+ "summary_short"=>"u201cConstantineu2019s Swordu201d asks: When your core beliefs conflict with church doctrine, how far should your loyalty to the church extend?",
31
+ "thousand_best"=>"N",
32
+ "mpaa_rating"=>"NR",
33
+ "related_urls"=>[{"suggested_link_text"=>"Overview of Constantine's Sword", "url"=>"http://movies.nytimes.com/movie/405736/Constantine-s-Sword/overview", "type"=>"overview"}, {"suggested_link_text"=>"Tickets & Showtimes for Constantine's Sword", "url"=>"http://movies.nytimes.com/movie/405736/Constantine-s-Sword/showtimes", "type"=>"showtimes"}, {"suggested_link_text"=>"Cast, Credits & Awards for Constantine's Sword", "url"=>"http://movies.nytimes.com/movie/405736/Constantine-s-Sword/details", "type"=>"awards"}, {"suggested_link_text"=>"Readers' Reviews of Constantine's Sword", "url"=>"http://movies.nytimes.com/movie/405736/Constantine-s-Sword/rnr", "type"=>"community"}, {"suggested_link_text"=>"Trailers & Clips for Constantine's Sword", "url"=>"http://movies.nytimes.com/movie/405736/Constantine-s-Sword/trailers", "type"=>"trailers"}],
34
+ "seo-name"=>"Constantine-s-Sword",
35
+ "display_title"=>"Constantine's Sword",
36
+ "link"=>{"suggested_link_text"=>"Read the New York Times Review of Constantine's Sword", "url"=>"http://movies.nytimes.com/", "type"=>"article"},
37
+ "date_updated"=>"2008-08-21 12:10:38"}
38
+
39
+ MOVIE_RESULT_HASH = {
40
+ "status" => "OK",
41
+ "copyright" => "Copyright (c) 2008 The New York Times Company. All Rights Reserved.",
42
+ "num_results" => 1,
43
+ "results" => [MOVIE_REVIEW_HASH]
44
+ }
45
+
46
+ MOVIE_RESULT_REPLY = MOVIE_RESULT_HASH.to_json
metadata ADDED
@@ -0,0 +1,82 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: nytimes-movies
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - Jacob Harris
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-02-12 00:00:00 -05:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: json
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0"
24
+ version:
25
+ description: A gem for talking to the New York Times Movie Reviews API
26
+ email: jharris@nytimes.com
27
+ executables: []
28
+
29
+ extensions: []
30
+
31
+ extra_rdoc_files: []
32
+
33
+ files:
34
+ - History.txt
35
+ - Rakefile
36
+ - README.rdoc
37
+ - VERSION.yml
38
+ - lib/nytimes/movies/base.rb
39
+ - lib/nytimes/movies/critic.rb
40
+ - lib/nytimes/movies/link.rb
41
+ - lib/nytimes/movies/multimedia_link.rb
42
+ - lib/nytimes/movies/result_set.rb
43
+ - lib/nytimes/movies/review.rb
44
+ - lib/nytimes/movies.rb
45
+ - lib/nytimes-movies.rb
46
+ - test/nytimes/movies/test_critic.rb
47
+ - test/nytimes/movies/test_link.rb
48
+ - test/nytimes/movies/test_multimedia_link.rb
49
+ - test/nytimes/movies/test_result_set.rb
50
+ - test/nytimes/movies/test_review.rb
51
+ - test/test_helper.rb
52
+ has_rdoc: true
53
+ homepage: http://github.com/harrisj/nytimes-moves
54
+ licenses: []
55
+
56
+ post_install_message:
57
+ rdoc_options:
58
+ - --inline-source
59
+ - --charset=UTF-8
60
+ require_paths:
61
+ - lib
62
+ required_ruby_version: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - ">="
65
+ - !ruby/object:Gem::Version
66
+ version: "0"
67
+ version:
68
+ required_rubygems_version: !ruby/object:Gem::Requirement
69
+ requirements:
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ version: "0"
73
+ version:
74
+ requirements: []
75
+
76
+ rubyforge_project:
77
+ rubygems_version: 1.3.2
78
+ signing_key:
79
+ specification_version: 2
80
+ summary: A gem for talking to the New York Times Movie Reviews API
81
+ test_files: []
82
+