Skip to content

Commit

Permalink
Fix enumerable search interface
Browse files Browse the repository at this point in the history
  • Loading branch information
sferik committed Dec 24, 2013
1 parent 141d237 commit e14cc33
Show file tree
Hide file tree
Showing 10 changed files with 74 additions and 233 deletions.
2 changes: 1 addition & 1 deletion Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ end

require 'yardstick/rake/verify'
Yardstick::Rake::Verify.new do |verify|
verify.threshold = 60.1
verify.threshold = 59.9
end

task :default => [:spec, :rubocop, :verify_measurements]
32 changes: 5 additions & 27 deletions lib/twitter/cursor.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
require 'twitter/enumerable'

module Twitter
class Cursor
include Enumerable
include Twitter::Enumerable
attr_reader :attrs
alias_method :to_h, :attrs
alias_method :to_hash, :attrs
Expand Down Expand Up @@ -32,7 +34,7 @@ def from_response(response, key, klass, client, request_method, path, options) #
# @param path [String]
# @param options [Hash]
# @return [Twitter::Cursor]
def initialize(attrs, key, klass, client, request_method, path, options) # rubocop:disable ParameterLists
def initialize(attrs, key, klass, client, request_method, path, options = {}) # rubocop:disable ParameterLists
@key = key.to_sym
@klass = klass
@client = client
Expand All @@ -43,42 +45,18 @@ def initialize(attrs, key, klass, client, request_method, path, options) # ruboc
set_attrs(attrs)
end

# @return [Enumerator]
def each(start = 0, &block)
return to_enum(:each) unless block_given?
Array(@collection[start..-1]).each do |element|
yield element
end
unless last?
start = [@collection.size, start].max
fetch_next_page
each(start, &block)
end
self
end
private

def next_cursor
@attrs[:next_cursor] || -1
end
alias_method :next, :next_cursor

def previous_cursor
@attrs[:previous_cursor]
end
alias_method :previous, :previous_cursor

# @return [Boolean]
def first?
previous_cursor.zero?
end

# @return [Boolean]
def last?
next_cursor.zero?
end

private

def fetch_next_page
response = @client.send(@request_method, @path, @options.merge(:cursor => next_cursor))
set_attrs(response[:body])
Expand Down
10 changes: 10 additions & 0 deletions lib/twitter/enumerable.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,17 @@ def each(start = 0, &block)
Array(@collection[start..-1]).each do |element|
yield element
end
unless last?
start = [@collection.size, start].max
fetch_next_page
each(start, &block)
end
self
end

# @return [Boolean]
def last?
true
end
end
end
9 changes: 8 additions & 1 deletion lib/twitter/rest/api/search.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,14 @@ module Search
# @option options [Boolean, String, Integer] :include_entities The tweet entities node will be disincluded when set to false.
# @return [Twitter::SearchResults] Return tweets that match a specified query with search metadata
def search(q, options = {})
object_from_response(Twitter::SearchResults, :get, '/1.1/search/tweets.json', options.merge(:q => q))
search_results_from_response(:get, '/1.1/search/tweets.json', options.merge(:q => q))
end

private

def search_results_from_response(request_method, path, options = {}) # rubocop:disable ParameterLists
response = send(request_method.to_sym, path, options)
Twitter::SearchResults.from_response(response, self, request_method, path, options)
end
end
end
Expand Down
77 changes: 31 additions & 46 deletions lib/twitter/search_results.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,60 +12,44 @@ class << self
# Construct a new SearchResults object from a response hash
#
# @param response [Hash]
# @return [Twitter::Base]
def from_response(response = {})
new(response[:body])
# @param client [Twitter::REST::Client]
# @param path [String]
# @return [Twitter::SearchResults]
def from_response(response, client, request_method, path, options) # rubocop:disable ParameterLists
new(response[:body], client, request_method, path, options)
end
end

# Initializes a new SearchResults object
#
# @param attrs [Hash]
# @param client [Twitter::REST::Client]
# @param request_method [String, Symbol]
# @param path [String]
# @param options [Hash]
# @return [Twitter::SearchResults]
def initialize(attrs = {})
@attrs = attrs
@collection = Array(@attrs[:statuses]).map do |tweet|
Tweet.new(tweet)
end
end

# @return [Float]
def completed_in
@attrs[:search_metadata][:completed_in] if @attrs[:search_metadata]
end

# @return [Integer]
def max_id
@attrs[:search_metadata][:max_id] if @attrs[:search_metadata]
end

# @return [Integer]
def page
@attrs[:search_metadata][:page] if @attrs[:search_metadata]
end

# @return [String]
def query
@attrs[:search_metadata][:query] if @attrs[:search_metadata]
def initialize(attrs, client, request_method, path, options = {}) # rubocop:disable ParameterLists
@client = client
@request_method = request_method.to_sym
@path = path
@options = options
@collection = []
set_attrs(attrs)
end

# @return [Integer]
def results_per_page
@attrs[:search_metadata][:count] if @attrs[:search_metadata]
end
alias_method :rpp, :results_per_page
alias_method :count, :results_per_page
private

# @return [Integer]
def since_id
@attrs[:search_metadata][:since_id] if @attrs[:search_metadata]
# @return [Boolean]
def last?
!next_results?
end

# @return [Boolean]
def next_results?
!!(@attrs[:search_metadata] && @attrs[:search_metadata][:next_results])
end
alias_method :next_page?, :next_results?
alias_method :next?, :next_results?

# Returns a Hash of query parameters for the next result in the search
#
Expand All @@ -78,18 +62,19 @@ def next_results
end
end
alias_method :next_page, :next_results
alias_method :next, :next_results

# Returns a Hash of query parameters for the refresh URL in the search
#
# @note Returned Hash can be merged into the previous search options list to easily access the refresh page.
# @return [Hash] The parameters needed to refresh the page.
def refresh_results
query_string = strip_first_character(@attrs[:search_metadata][:refresh_url])
query_string_to_hash(query_string)
def fetch_next_page
response = @client.send(@request_method, @path, next_page)
set_attrs(response[:body])
end
alias_method :refresh_page, :refresh_results

private
def set_attrs(attrs)
@attrs = attrs
Array(@attrs[:statuses]).map do |tweet|

This comment has been minimized.

Copy link
@maxwell

maxwell Jan 23, 2014

I could be wrong, but I think since @attrs is just a Hash, and not a HashWithIndiffernet Access, @attrs[:statueses] is always nil, which means @collection is always empty, which would mean all the Enumerables would be missing

This comment has been minimized.

Copy link
@sferik

sferik Jan 23, 2014

Author Owner

What makes you think the statuses key is a String as opposed to a Symbol? When the JSON from the Twitter API gets parsed into a Hash, I send it the :symbolize_names => true option, so all the keys (including statuses) should be symbols and @collection should not be empty (unless there are actually no search results). The specs verify that this is true.

Can you open an issue for your code that’s producing empty search results?

This comment has been minimized.

Copy link
@sferik

sferik Jan 23, 2014

Author Owner

It appears that the symbolize_name option was added to the json gem in version 1.2.1.

Is it possible you’re using an older version? (It should not be possible, since twitter specifies a dependency on json version 1.8 or higher.)

@collection << Tweet.new(tweet)
end
end

# Returns the string with the first character removed
#
Expand Down
2 changes: 1 addition & 1 deletion spec/fixtures/search.json
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"statuses":[{"metadata":{"result_type":"recent","iso_language_code":"en"},"created_at":"Mon Sep 17 22:41:52 +0000 2012","id":247827742178021376,"id_str":"247827742178021376","text":"Bubble Mailer #freebandnames","source":"\u003ca href=\"http:\/\/tapbots.com\" rel=\"nofollow\"\u003eTweetbot for Mac\u003c\/a\u003e","truncated":false,"in_reply_to_status_id":null,"in_reply_to_status_id_str":null,"in_reply_to_user_id":null,"in_reply_to_user_id_str":null,"in_reply_to_screen_name":null,"user":{"id":671293,"id_str":"671293","name":"Richard Allen","screen_name":"richrad","location":"Kansas City","url":"http:\/\/richardallen.me","description":"Give me a break, Jeffery.","protected":false,"followers_count":174,"friends_count":273,"listed_count":2,"created_at":"Sat Jan 20 16:02:45 +0000 2007","favourites_count":383,"utc_offset":-21600,"time_zone":"Central Time (US & Canada)","geo_enabled":false,"verified":false,"statuses_count":1370,"lang":"en","contributors_enabled":false,"is_translator":false,"profile_background_color":"0099B9","profile_background_image_url":"http:\/\/a0.twimg.com\/images\/themes\/theme4\/bg.gif","profile_background_image_url_https":"https:\/\/si0.twimg.com\/images\/themes\/theme4\/bg.gif","profile_background_tile":false,"profile_image_url":"http:\/\/a0.twimg.com\/profile_images\/1399177739\/notlookingup_normal.jpeg","profile_image_url_https":"https:\/\/si0.twimg.com\/profile_images\/1399177739\/notlookingup_normal.jpeg","profile_link_color":"0099B9","profile_sidebar_border_color":"5ED4DC","profile_sidebar_fill_color":"95E8EC","profile_text_color":"3C3940","profile_use_background_image":true,"show_all_inline_media":true,"default_profile":false,"default_profile_image":false,"following":null,"follow_request_sent":null,"notifications":null},"geo":null,"coordinates":null,"place":null,"contributors":null,"retweet_count":0,"entities":{"hashtags":[{"text":"freebandnames","indices":[14,28]}],"urls":[],"user_mentions":[]},"favorited":false,"retweeted":false}],"search_metadata":{"completed_in":0.029,"max_id":250126199840518145,"max_id_str":"250126199840518145","next_page":"?page=2&max_id=250126199840518145&q=%23freebandnames&rpp=4&include_entities=1&result_type=mixed","page":1,"query":"%23freebandnames","refresh_url":"?since_id=250126199840518145&q=%23freebandnames&result_type=mixed&include_entities=1","count":4,"since_id":24012619984051000,"since_id_str":"24012619984051000"}}
{"statuses":[{"metadata":{"result_type":"recent","iso_language_code":"en"},"created_at":"Fri Dec 20 16:52:25 +0000 2013","id":414075829182676992,"id_str":"414075829182676992","text":"@Just_Reboot #FreeBandNames mono surround","source":"\u003ca href=\"http:\/\/www.myplume.com\/\" rel=\"nofollow\"\u003ePlume\u00a0for\u00a0Android\u003c\/a\u003e","truncated":false,"in_reply_to_status_id":414069174856843264,"in_reply_to_status_id_str":"414069174856843264","in_reply_to_user_id":29296581,"in_reply_to_user_id_str":"29296581","in_reply_to_screen_name":"Just_Reboot","user":{"id":546527520,"id_str":"546527520","name":"Phil Empanada","screen_name":"ItsFuckinOhSo","location":"By cacti and sand","description":"Insert cheesy warnings about this account","url":null,"entities":{"description":{"urls":[]}},"protected":false,"followers_count":257,"friends_count":347,"listed_count":10,"created_at":"Fri Apr 06 02:15:16 +0000 2012","favourites_count":345,"utc_offset":-25200,"time_zone":"Arizona","geo_enabled":false,"verified":false,"statuses_count":21765,"lang":"en","contributors_enabled":false,"is_translator":false,"profile_background_color":"C0DEED","profile_background_image_url":"http:\/\/abs.twimg.com\/images\/themes\/theme1\/bg.png","profile_background_image_url_https":"https:\/\/abs.twimg.com\/images\/themes\/theme1\/bg.png","profile_background_tile":false,"profile_image_url":"http:\/\/pbs.twimg.com\/profile_images\/414512901680930816\/AcmN-ByT_normal.jpeg","profile_image_url_https":"https:\/\/pbs.twimg.com\/profile_images\/414512901680930816\/AcmN-ByT_normal.jpeg","profile_banner_url":"https:\/\/pbs.twimg.com\/profile_banners\/546527520\/1371244104","profile_link_color":"0084B4","profile_sidebar_border_color":"C0DEED","profile_sidebar_fill_color":"DDEEF6","profile_text_color":"333333","profile_use_background_image":true,"default_profile":true,"default_profile_image":false,"following":false,"follow_request_sent":false,"notifications":false},"geo":null,"coordinates":null,"place":null,"contributors":null,"retweet_count":0,"favorite_count":0,"entities":{"hashtags":[{"text":"FreeBandNames","indices":[13,27]}],"symbols":[],"urls":[],"user_mentions":[{"screen_name":"Just_Reboot","name":"Reboot","id":29296581,"id_str":"29296581","indices":[0,12]}]},"favorited":false,"retweeted":false,"lang":"en"},{"metadata":{"result_type":"recent","iso_language_code":"en"},"created_at":"Fri Dec 20 16:43:32 +0000 2013","id":414073595372265472,"id_str":"414073595372265472","text":"RT @Just_Reboot: The Dick Tonsils #FreeBandNames\u00a0","source":"\u003ca href=\"http:\/\/twitter.com\/download\/android\" rel=\"nofollow\"\u003eTwitter for Android\u003c\/a\u003e","truncated":false,"in_reply_to_status_id":null,"in_reply_to_status_id_str":null,"in_reply_to_user_id":null,"in_reply_to_user_id_str":null,"in_reply_to_screen_name":null,"user":{"id":109670150,"id_str":"109670150","name":"Derek Rodriguez","screen_name":"drod2169","location":"Tampa, Florida","description":"Web Developer, College Student, Workout Addict, Taco Fiend. #FBGT","url":null,"entities":{"description":{"urls":[]}},"protected":false,"followers_count":3119,"friends_count":714,"listed_count":142,"created_at":"Fri Jan 29 21:29:16 +0000 2010","favourites_count":3728,"utc_offset":-18000,"time_zone":"Eastern Time (US & Canada)","geo_enabled":false,"verified":false,"statuses_count":46991,"lang":"en","contributors_enabled":false,"is_translator":false,"profile_background_color":"C0DEED","profile_background_image_url":"http:\/\/abs.twimg.com\/images\/themes\/theme1\/bg.png","profile_background_image_url_https":"https:\/\/abs.twimg.com\/images\/themes\/theme1\/bg.png","profile_background_tile":false,"profile_image_url":"http:\/\/pbs.twimg.com\/profile_images\/3347464379\/1046c92ab1834f1a3c1692ea585a1d69_normal.jpeg","profile_image_url_https":"https:\/\/pbs.twimg.com\/profile_images\/3347464379\/1046c92ab1834f1a3c1692ea585a1d69_normal.jpeg","profile_banner_url":"https:\/\/pbs.twimg.com\/profile_banners\/109670150\/1368729387","profile_link_color":"0084B4","profile_sidebar_border_color":"C0DEED","profile_sidebar_fill_color":"DDEEF6","profile_text_color":"333333","profile_use_background_image":true,"default_profile":true,"default_profile_image":false,"following":false,"follow_request_sent":false,"notifications":false},"geo":null,"coordinates":null,"place":null,"contributors":null,"retweeted_status":{"metadata":{"result_type":"recent","iso_language_code":"en"},"created_at":"Fri Dec 20 16:34:40 +0000 2013","id":414071361066532864,"id_str":"414071361066532864","text":"The Dick Tonsils #FreeBandNames\u00a0","source":"\u003ca href=\"http:\/\/www.myplume.com\/\" rel=\"nofollow\"\u003ePlume\u00a0for\u00a0Android\u003c\/a\u003e","truncated":false,"in_reply_to_status_id":null,"in_reply_to_status_id_str":null,"in_reply_to_user_id":null,"in_reply_to_user_id_str":null,"in_reply_to_screen_name":null,"user":{"id":29296581,"id_str":"29296581","name":"Reboot","screen_name":"Just_Reboot","location":"NC","description":"G+ http:\/\/t.co\/MyXmhCAtnD\r\n#TeamKang PR, Attempted Writer, IT Tech, Social Media Berserker and Entertainer, Adjectives, Assertions, Disclaimers","url":null,"entities":{"description":{"urls":[{"url":"http:\/\/t.co\/MyXmhCAtnD","expanded_url":"http:\/\/goo.gl\/giVTc","display_url":"goo.gl\/giVTc","indices":[3,25]}]}},"protected":false,"followers_count":2244,"friends_count":435,"listed_count":60,"created_at":"Mon Apr 06 21:21:27 +0000 2009","favourites_count":79,"utc_offset":-18000,"time_zone":"Eastern Time (US & Canada)","geo_enabled":false,"verified":false,"statuses_count":27703,"lang":"en","contributors_enabled":false,"is_translator":false,"profile_background_color":"131516","profile_background_image_url":"http:\/\/a0.twimg.com\/profile_background_images\/871578268\/5db26751bf732750f5ca1ed4f9f59309.png","profile_background_image_url_https":"https:\/\/si0.twimg.com\/profile_background_images\/871578268\/5db26751bf732750f5ca1ed4f9f59309.png","profile_background_tile":true,"profile_image_url":"http:\/\/pbs.twimg.com\/profile_images\/378800000480531175\/93e8b5cb95a635ebb81302f5b6044a76_normal.jpeg","profile_image_url_https":"https:\/\/pbs.twimg.com\/profile_images\/378800000480531175\/93e8b5cb95a635ebb81302f5b6044a76_normal.jpeg","profile_banner_url":"https:\/\/pbs.twimg.com\/profile_banners\/29296581\/1368898490","profile_link_color":"009999","profile_sidebar_border_color":"FFFFFF","profile_sidebar_fill_color":"EFEFEF","profile_text_color":"333333","profile_use_background_image":true,"default_profile":false,"default_profile_image":false,"following":false,"follow_request_sent":false,"notifications":false},"geo":null,"coordinates":null,"place":null,"contributors":null,"retweet_count":1,"favorite_count":1,"entities":{"hashtags":[{"text":"FreeBandNames","indices":[17,31]}],"symbols":[],"urls":[],"user_mentions":[]},"favorited":false,"retweeted":false,"lang":"en"},"retweet_count":1,"favorite_count":0,"entities":{"hashtags":[{"text":"FreeBandNames","indices":[34,48]}],"symbols":[],"urls":[],"user_mentions":[{"screen_name":"Just_Reboot","name":"Reboot","id":29296581,"id_str":"29296581","indices":[3,15]}]},"favorited":false,"retweeted":false,"lang":"en"},{"metadata":{"result_type":"recent","iso_language_code":"en"},"created_at":"Fri Dec 20 16:34:40 +0000 2013","id":414071361066532864,"id_str":"414071361066532864","text":"The Dick Tonsils #FreeBandNames\u00a0","source":"\u003ca href=\"http:\/\/www.myplume.com\/\" rel=\"nofollow\"\u003ePlume\u00a0for\u00a0Android\u003c\/a\u003e","truncated":false,"in_reply_to_status_id":null,"in_reply_to_status_id_str":null,"in_reply_to_user_id":null,"in_reply_to_user_id_str":null,"in_reply_to_screen_name":null,"user":{"id":29296581,"id_str":"29296581","name":"Reboot","screen_name":"Just_Reboot","location":"NC","description":"G+ http:\/\/t.co\/MyXmhCAtnD\r\n#TeamKang PR, Attempted Writer, IT Tech, Social Media Berserker and Entertainer, Adjectives, Assertions, Disclaimers","url":null,"entities":{"description":{"urls":[{"url":"http:\/\/t.co\/MyXmhCAtnD","expanded_url":"http:\/\/goo.gl\/giVTc","display_url":"goo.gl\/giVTc","indices":[3,25]}]}},"protected":false,"followers_count":2244,"friends_count":435,"listed_count":60,"created_at":"Mon Apr 06 21:21:27 +0000 2009","favourites_count":79,"utc_offset":-18000,"time_zone":"Eastern Time (US & Canada)","geo_enabled":false,"verified":false,"statuses_count":27703,"lang":"en","contributors_enabled":false,"is_translator":false,"profile_background_color":"131516","profile_background_image_url":"http:\/\/a0.twimg.com\/profile_background_images\/871578268\/5db26751bf732750f5ca1ed4f9f59309.png","profile_background_image_url_https":"https:\/\/si0.twimg.com\/profile_background_images\/871578268\/5db26751bf732750f5ca1ed4f9f59309.png","profile_background_tile":true,"profile_image_url":"http:\/\/pbs.twimg.com\/profile_images\/378800000480531175\/93e8b5cb95a635ebb81302f5b6044a76_normal.jpeg","profile_image_url_https":"https:\/\/pbs.twimg.com\/profile_images\/378800000480531175\/93e8b5cb95a635ebb81302f5b6044a76_normal.jpeg","profile_banner_url":"https:\/\/pbs.twimg.com\/profile_banners\/29296581\/1368898490","profile_link_color":"009999","profile_sidebar_border_color":"FFFFFF","profile_sidebar_fill_color":"EFEFEF","profile_text_color":"333333","profile_use_background_image":true,"default_profile":false,"default_profile_image":false,"following":false,"follow_request_sent":false,"notifications":false},"geo":null,"coordinates":null,"place":null,"contributors":null,"retweet_count":1,"favorite_count":1,"entities":{"hashtags":[{"text":"FreeBandNames","indices":[17,31]}],"symbols":[],"urls":[],"user_mentions":[]},"favorited":false,"retweeted":false,"lang":"en"}],"search_metadata":{"completed_in":0.012,"max_id":414075829182676992,"max_id_str":"414075829182676992","next_results":"?max_id=414071361066532863&q=%23freebandnames&count=3&include_entities=1","query":"%23freebandnames","refresh_url":"?since_id=414075829182676992&q=%23freebandnames&include_entities=1","count":3,"since_id":0,"since_id_str":"0"}}
Loading

0 comments on commit e14cc33

Please sign in to comment.