twitter_friendly 0.2.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7e5481574a9af954eee677e12ab10be1cd29bd3e744c00f295792aa59b7dec18
4
- data.tar.gz: '078c67dafe0a4c76418d178abb1c8da2b79323790353fc47adf254ed7866e795'
3
+ metadata.gz: bd3aedc3bbfeb41ae92a95b3ca8a4a654c03d1332540396d85ff883871456f8d
4
+ data.tar.gz: 2554445ef8a39dce0be467d006f9f1fea19aa6ce265a0612e71350f27aca3007
5
5
  SHA512:
6
- metadata.gz: 142be53e34255b50211248e835bdec011fe68cd7ffede00929a762228ffb854de53616f4ad5875d23e77283d2d1ece8a890f3dc3f83c86e71e907bacd52163cc
7
- data.tar.gz: 8734fe7505f898ce41ab563774e515baee99d7fc8fef2ff42803d575d8d74d03267a7f2468ffa7e691015040d38779b7e23254c814523caf5eb71b3c2c4312c0
6
+ metadata.gz: 69ce0e002321247a430df087a6efad1868c5fd7397c86940f8c4bf9043e089c8d7077acda3699926d29b8b69d9b24cc511313e072f515e2897c62cbdc6df3927
7
+ data.tar.gz: a2b958451a785d063b23becc9bde00c3fb52c89f25e646a95b4c98126c2969771ad3a8b7fed3ee80e64cc8618ba0f1f99dd3fdd9121f01c3f1075071e564789a
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- twitter_friendly (0.2.1)
4
+ twitter_friendly (0.3.0)
5
5
  activesupport (>= 4.2, < 6.0)
6
6
  oj (~> 3.7.6)
7
7
  parallel (~> 1.12.1)
@@ -14,33 +14,30 @@ module TwitterFriendly
14
14
  @client = ::ActiveSupport::Cache::FileStore.new(path, options)
15
15
  end
16
16
 
17
- def fetch(method, user, options = {}, &block)
18
- key = CacheKey.gen(method, user, options.except(:args))
19
- super_operation = options[:args].length >= 2 && options[:args][1][:super_operation]
20
-
17
+ # @param key [String]
18
+ #
19
+ # @option serialize_options [Array] :args
20
+ def fetch(key, serialize_options, &block)
21
21
  block_result = nil
22
- blk =
22
+ yield_and_encode =
23
23
  Proc.new do
24
24
  block_result = yield
25
- encode(block_result, args: options[:args])
25
+ encode(block_result, serialize_options)
26
26
  end
27
27
 
28
- fetch_result =
29
- if super_operation
30
- @client.fetch(key, tf_super_operation: super_operation, &blk)
31
- else
32
- @client.fetch(key, &blk)
33
- end
28
+ fetch_result = @client.fetch(key, &yield_and_encode)
34
29
 
35
- block_result ? block_result : decode(fetch_result, args: options[:args])
30
+ block_result || decode(fetch_result, serialize_options)
36
31
  end
37
32
 
38
33
  private
39
34
 
35
+ # @option options [Array] :args
40
36
  def encode(obj, options)
41
37
  Serializer.encode(obj, options)
42
38
  end
43
39
 
40
+ # @option options [Array] :args
44
41
  def decode(str, options)
45
42
  Serializer.decode(str, options)
46
43
  end
@@ -25,8 +25,8 @@ module TwitterFriendly
25
25
  when method == :search then "query#{DELIM}#{user}"
26
26
  when method == :friendship? then "from#{DELIM}#{user[0]}#{DELIM}to#{DELIM}#{user[1]}"
27
27
  when method == :list_members then "list_id#{DELIM}#{user}"
28
- when method == :collect_with_max_id then method_identifier(options[:super_operation], user, options)
29
- when method == :collect_with_cursor then method_identifier(options[:super_operation], user, options)
28
+ when method == :collect_with_max_id then method_identifier(extract_super_operation(options), user, options)
29
+ when method == :collect_with_cursor then method_identifier(extract_super_operation(options), user, options)
30
30
  when user.nil? && options[:hash].present? then "token-hash#{DELIM}#{options[:hash]}"
31
31
  else user_identifier(user)
32
32
  end
@@ -46,7 +46,7 @@ module TwitterFriendly
46
46
  def options_identifier(method, options)
47
47
  # TODO 内部的な値はすべてprefix _tf_ をつける
48
48
  opt = options.except(:hash, :call_count, :call_limit, :super_operation, :super_super_operation, :recursive, :parallel)
49
- opt[:in] = options[:super_operation] if %i(collect_with_max_id collect_with_cursor).include?(method)
49
+ opt[:in] = extract_super_operation(options) if %i(collect_with_max_id collect_with_cursor).include?(method)
50
50
 
51
51
  if opt.empty?
52
52
  nil
@@ -56,6 +56,15 @@ module TwitterFriendly
56
56
  end
57
57
  end
58
58
 
59
+ def extract_super_operation(options)
60
+ raise ArgumentError.new('You must specify :super_operation.') unless options[:super_operation]
61
+ if options[:super_operation].is_a?(Array)
62
+ options[:super_operation][0]
63
+ else
64
+ options[:super_operation]
65
+ end
66
+ end
67
+
59
68
  def hexdigest(ary)
60
69
  Digest::MD5.hexdigest(ary.join(','))
61
70
  end
@@ -0,0 +1,73 @@
1
+ module TwitterFriendly
2
+ module CachingAndLogging
3
+
4
+ module_function
5
+
6
+ # TODO 1つのメソッドに対して1回しか実行されないようにする
7
+ # 全体をキャッシュさせ、さらにロギングを行う
8
+ def caching(*root_args)
9
+ root_args.each do |method_name|
10
+ define_method(method_name) do |*args|
11
+ options = args.extract_options!
12
+ Instrumenter.start_processing(method_name, options)
13
+
14
+ Instrumenter.complete_processing(method_name, options) do
15
+ do_request =
16
+ Proc.new {Instrumenter.perform_request(method_name, options) {options.empty? ? super(*args) : super(*args, options)}}
17
+
18
+ if Utils.cache_disabled?(options)
19
+ do_request.call
20
+ else
21
+ user = (method_name == :friendship?) ? args[0, 2] : args[0]
22
+ key = CacheKey.gen(method_name, user, options.merge(hash: credentials_hash))
23
+ @cache.fetch(key, args: [method_name, options], &do_request)
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+
30
+ # 全体をキャッシュせずにロギングだけを行う
31
+ def logging(*root_args)
32
+ root_args.each do |method_name|
33
+ define_method(method_name) do |*args|
34
+ options = args.extract_options!
35
+ Instrumenter.start_processing(method_name, options)
36
+
37
+ Instrumenter.complete_processing(method_name, options) do
38
+ options.empty? ? super(*args) : super(*args, options)
39
+ end
40
+ end
41
+ end
42
+ end
43
+
44
+ module Instrumenter
45
+
46
+ module_function
47
+
48
+ def start_processing(operation, options)
49
+ payload = {operation: operation}.merge(options)
50
+ ::ActiveSupport::Notifications.instrument('start_processing.twitter_friendly', payload) {}
51
+ end
52
+
53
+ def complete_processing(operation, options)
54
+ payload = {operation: operation}.merge(options)
55
+ ::ActiveSupport::Notifications.instrument('complete_processing.twitter_friendly', payload) { yield(payload) }
56
+ end
57
+
58
+ def perform_request(caller, options, &block)
59
+ payload = {operation: 'request', args: [caller, options]}
60
+ ::ActiveSupport::Notifications.instrument('request.twitter_friendly', payload) { yield(payload) }
61
+ end
62
+ end
63
+
64
+ module Utils
65
+
66
+ module_function
67
+
68
+ def cache_disabled?(options)
69
+ options.is_a?(Hash) && options.has_key?(:cache) && !options[:cache]
70
+ end
71
+ end
72
+ end
73
+ end
@@ -1,17 +1,29 @@
1
1
  require 'forwardable'
2
2
 
3
+ require 'twitter_friendly/caching_and_logging'
3
4
  require 'twitter_friendly/rest/api'
5
+ require 'twitter_friendly/utils'
4
6
 
5
7
  module TwitterFriendly
6
8
  class Client
7
9
  extend Forwardable
8
10
  def_delegators :@twitter, :access_token, :access_token_secret, :consumer_key, :consumer_secret
9
11
 
12
+ include TwitterFriendly::Utils
10
13
  include TwitterFriendly::REST::API
11
14
  include TwitterFriendly::RateLimit
12
15
 
16
+ extend TwitterFriendly::CachingAndLogging
17
+ caching :user, :friendship?, :verify_credentials, :user?, :blocked_ids
18
+ logging :favorites, :friend_ids, :follower_ids, :friends, :followers, :friend_ids_and_follower_ids, :friends_and_followers,
19
+ :home_timeline, :user_timeline, :mentions_timeline, :search, :memberships, :list_members, :retweeters_ids
20
+
13
21
  def initialize(*args)
14
22
  options = args.extract_options!
23
+ @twitter = Twitter::REST::Client.new(options.slice(:access_token, :access_token_secret, :consumer_key, :consumer_secret))
24
+
25
+ options.except!(:access_token, :access_token_secret, :consumer_key, :consumer_secret)
26
+ @cache = TwitterFriendly::Cache.new(options)
15
27
 
16
28
  @logger = TwitterFriendly::Logger.new(options)
17
29
 
@@ -23,9 +35,6 @@ module TwitterFriendly
23
35
  TwitterFriendly::ASLogSubscriber.attach_to :active_support
24
36
  end
25
37
  end
26
-
27
- @cache = TwitterFriendly::Cache.new(options)
28
- @twitter = Twitter::REST::Client.new(options)
29
38
  end
30
39
 
31
40
  def cache
@@ -14,8 +14,11 @@ module TwitterFriendly
14
14
  {args: args}.merge(payload.except(:args)).inspect
15
15
  end
16
16
 
17
- def nested_indent(payload)
18
- (payload[:super_operation] ? ' ' : '') + (payload[:super_super_operation] ? ' ' : '') + (payload[:tf_super_operation] ? ' ' : '')
17
+ INDENT = ' '
18
+
19
+ def indentation(payload)
20
+ sp = payload[:super_operation]&.is_a?(Array) ? (INDENT * payload[:super_operation].size) : ''
21
+ sp + (payload[:name] == 'write' ? INDENT : '')
19
22
  end
20
23
 
21
24
  module_function
@@ -33,11 +36,12 @@ module TwitterFriendly
33
36
  include Logging
34
37
 
35
38
  def start_processing(event)
36
- payload = event.payload
37
- name = "#{nested_indent(payload)}TF::Started #{payload.delete(:operation)}"
38
39
  debug do
40
+ payload = event.payload
41
+ name = "#{indentation(payload)}TF::Started #{payload[:operation]}"
42
+
39
43
  if payload[:super_operation]
40
- "#{name} in #{payload[:super_operation]} at #{Time.now}"
44
+ "#{name} in #{payload[:super_operation][0]} at #{Time.now}"
41
45
  else
42
46
  "#{name} at #{Time.now}"
43
47
  end
@@ -45,36 +49,38 @@ module TwitterFriendly
45
49
  end
46
50
 
47
51
  def complete_processing(event)
48
- payload = event.payload
49
- name = "TF::Completed #{payload.delete(:operation)} in #{event.duration.round(1)}ms"
50
52
  debug do
51
- "#{nested_indent(payload)}#{name}#{" #{truncated_payload(payload)}" unless payload.empty?}"
53
+ payload = event.payload
54
+ name = "TF::Completed #{payload[:operation]} in #{event.duration.round(1)}ms"
55
+
56
+ "#{indentation(payload)}#{name}#{" #{truncated_payload(payload)}" unless payload.empty?}"
57
+ end
58
+ end
59
+
60
+ def collect(event)
61
+ debug do
62
+ payload = event.payload
63
+ payload.delete(:name)
64
+ operation = payload.delete(:operation)
65
+ name = " TW::#{operation.capitalize} #{payload[:args].last[:super_operation][0]} in #{payload[:args][0]} (#{event.duration.round(1)}ms)"
66
+ name = color(name, BLUE, true)
67
+ " #{indentation(payload)}#{name}#{" #{payload[:args][1]}" unless payload.empty?}"
52
68
  end
53
69
  end
54
70
 
55
71
  def twitter_friendly_any(event)
56
- payload = event.payload
57
- payload.delete(:name)
58
- operation = payload.delete(:operation)
59
- name =
60
- if operation.to_sym == :collect
61
- " TW::#{operation.capitalize} #{payload[:args].last[:super_operation]} in #{payload[:args][0]} (#{event.duration.round(1)}ms)"
62
- else
63
- " TW::#{operation.capitalize} #{payload[:args][0]} (#{event.duration.round(1)}ms)"
64
- end
65
- c =
66
- if %i(encode decode).include?(operation.to_sym)
67
- YELLOW
68
- elsif %i(collect).include?(operation.to_sym)
69
- BLUE
70
- else
71
- CYAN
72
- end
73
- name = color(name, c, true)
74
- debug { " #{nested_indent(payload)}#{name}#{" #{truncated_payload(payload)}" unless payload.empty?}" }
72
+ debug do
73
+ payload = event.payload
74
+ payload.delete(:name)
75
+ operation = payload.delete(:operation)
76
+ name = " TW::#{operation.capitalize} #{payload[:args][0]} (#{event.duration.round(1)}ms)"
77
+ c = (%i(encode decode).include?(operation.to_sym)) ? YELLOW : CYAN
78
+ name = color(name, c, true)
79
+ " #{indentation(payload)}#{name}#{" #{payload[:args][1]}" unless payload.empty?}"
80
+ end
75
81
  end
76
82
 
77
- %w(request encode decode collect).each do |operation|
83
+ %w(request encode decode).each do |operation|
78
84
  class_eval <<-METHOD, __FILE__, __LINE__ + 1
79
85
  def #{operation}(event)
80
86
  event.payload[:name] = '#{operation}'
@@ -88,12 +94,15 @@ module TwitterFriendly
88
94
  include Logging
89
95
 
90
96
  def cache_any(event)
91
- payload = event.payload
92
- operation = payload[:super_operation] == :fetch ? :fetch : payload[:name]
93
- hit = %i(read fetch).include?(operation.to_sym) && payload[:hit]
94
- name = " AS::#{operation.capitalize}#{' (Hit)' if hit} #{payload[:key].split(':')[1]} (#{event.duration.round(1)}ms)"
95
- name = color(name, MAGENTA, true)
96
- debug { "#{nested_indent(payload)}#{name} #{(payload.except(:name, :expires_in, :super_operation, :hit, :race_condition_ttl, :tf_super_operation).inspect)}" }
97
+ debug do
98
+ payload = event.payload
99
+ operation = payload[:super_operation] == :fetch ? :fetch : payload[:name]
100
+ hit = %i(read fetch).include?(operation.to_sym) && payload[:hit] ? ' (Hit)' : ''
101
+ name = " AS::#{operation.capitalize}#{hit} #{payload[:key].split(':')[1]} (#{event.duration.round(1)}ms)"
102
+ name = color(name, MAGENTA, true)
103
+ # :name, :expires_in, :super_operation, :hit, :race_condition_ttl, :tf_super_operation, :tf_super_super_operation
104
+ "#{indentation(payload)}#{name} #{(payload.slice(:key).inspect)}"
105
+ end
97
106
  end
98
107
 
99
108
  # Ignore generate and fetch_hit
@@ -10,7 +10,9 @@ require 'twitter_friendly/rest/favorites'
10
10
  require 'twitter_friendly/rest/lists'
11
11
  require 'twitter_friendly/rest/tweets'
12
12
 
13
- require 'twitter_friendly/caching'
13
+ # 後方互換性のために残した
14
+ require 'twitter_friendly/rest/extension/clusters'
15
+ require 'twitter_friendly/rest/extension/timelines'
14
16
 
15
17
  module TwitterFriendly
16
18
  module REST
@@ -27,8 +29,11 @@ module TwitterFriendly
27
29
  include TwitterFriendly::REST::Lists
28
30
  include TwitterFriendly::REST::Tweets
29
31
 
30
- include TwitterFriendly::Caching
32
+ include TwitterFriendly::REST::Extension::Clusters
33
+ include TwitterFriendly::REST::Extension::Timelines
34
+
31
35
  include TwitterFriendly::REST::Collector::Caching
36
+ include TwitterFriendly::REST::Users::Caching
32
37
  end
33
38
  end
34
39
  end
@@ -1,30 +1,30 @@
1
1
  module TwitterFriendly
2
2
  module REST
3
3
  module Base
4
- def fetch_tweets_with_max_id(name, args, max_count)
5
- options = args.extract_options!
4
+ def fetch_tweets_with_max_id(method_name, max_count, user, options)
6
5
  total_count = options.delete(:count) || max_count
7
6
  call_count = total_count / max_count + (total_count % max_count == 0 ? 0 : 1)
8
- options[:count] = max_count
7
+ options[:count] = [max_count, total_count].min
8
+ super_operation = options.delete(:super_operation)
9
+ collect_options = {call_count: call_count, total_count: total_count, super_operation: super_operation}
9
10
 
10
- collect_with_max_id(args[0], [], nil, {super_operation: name}.merge(options)) do |max_id|
11
+ collect_with_max_id(user, [], nil, options, collect_options) do |max_id|
11
12
  options[:max_id] = max_id unless max_id.nil?
12
- if (call_count -= 1) >= 0
13
- if name == :search
14
- @twitter.send(name, *args, options).attrs[:statuses]
15
- else
16
- @twitter.send(name, *args, options).map(&:attrs)
17
- end
18
- end
13
+
14
+ result = @twitter.send(method_name, *[user, options].compact)
15
+ (method_name == :search) ? result.attrs[:statuses] : result.map(&:attrs)
19
16
  end
20
17
  end
21
18
 
22
- def fetch_resources_with_cursor(name, args)
23
- options = args.extract_options!
24
-
25
- collect_with_cursor(args[0], [], -1, {super_operation: name}.merge(options)) do |next_cursor|
19
+ # @param method_name [Symbol]
20
+ # @param user [Integer, String, nil]
21
+ #
22
+ # @option options [Integer] :count
23
+ # @option options [String] :super_super_operation
24
+ def fetch_resources_with_cursor(method_name, user, options)
25
+ collect_with_cursor(user, [], -1, options) do |next_cursor|
26
26
  options[:cursor] = next_cursor unless next_cursor.nil?
27
- @twitter.send(name, *args, options)
27
+ @twitter.send(method_name, user, options.except(:super_operation))
28
28
  end
29
29
  end
30
30
  end
@@ -1,37 +1,39 @@
1
1
  module TwitterFriendly
2
2
  module REST
3
3
  module Collector
4
- def collect_with_max_id(user, collection, max_id, options, &block)
5
- fetch_options = options.dup
6
- fetch_options[:max_id] = max_id
7
- fetch_options.merge!(args: [__method__, fetch_options], hash: credentials_hash)
4
+ def collect_with_max_id(user, collection, max_id, options, collect_options, &block)
5
+ key = CacheKey.gen(__method__, user, options.merge(max_id: max_id, hash: credentials_hash, super_operation: collect_options[:super_operation]))
8
6
 
9
- # TODO Handle {cache: false} option
7
+ # TODO Handle {cache: false} option
10
8
  tweets =
11
- @cache.fetch(__method__, user, fetch_options) do
12
- Instrumenter.perform_request(args: [__method__, max_id: max_id, super_operation: options[:super_operation]]) do
13
- yield(max_id)
14
- end
9
+ @cache.fetch(key, args: [__method__, options]) do
10
+ Instrumenter.perform_request(__method__, options) {yield(max_id)}
15
11
  end
16
12
  return collection if tweets.nil?
17
13
 
18
- options[:recursive] = true
19
-
20
14
  collection.concat tweets
21
- tweets.empty? ? collection.flatten : collect_with_max_id(user, collection, tweets.last[:id] - 1, options, &block)
15
+ if tweets.empty? || (collect_options[:call_count] -= 1) < 1
16
+ collection.flatten
17
+ else
18
+ options[:recursive] = true
19
+ collect_with_max_id(user, collection, tweets.last[:id] - 1, options, collect_options, &block)
20
+ end
22
21
  end
23
22
 
23
+ # @param user [Integer, String, nil]
24
+ # @param collection [Array]
25
+ # @param cursor [Integer]
26
+ #
27
+ # @option options [Integer] :count
28
+ # @option options [String] :super_operation
29
+ # @option options [String] :super_super_operation
24
30
  def collect_with_cursor(user, collection, cursor, options, &block)
25
- fetch_options = options.dup
26
- fetch_options[:cursor] = cursor
27
- fetch_options.merge!(args: [__method__, fetch_options], hash: credentials_hash)
31
+ key = CacheKey.gen(__method__, user, options.merge(cursor: cursor, hash: credentials_hash))
28
32
 
29
33
  # TODO Handle {cache: false} option
30
34
  response =
31
- @cache.fetch(__method__, user, fetch_options) do
32
- Instrumenter.perform_request(args: [__method__, cursor: cursor, super_operation: options[:super_operation]]) do
33
- yield(cursor).attrs
34
- end
35
+ @cache.fetch(key, args: [__method__, options]) do
36
+ Instrumenter.perform_request(__method__, options) {yield(cursor).attrs}
35
37
  end
36
38
  return collection if response.nil?
37
39
 
@@ -49,8 +51,8 @@ module TwitterFriendly
49
51
  # 他のメソッドと違い再帰的に呼ばれるため、全体をキャッシュすると、すべてを再帰的にキャッシュしてしまう。
50
52
  # それを防ぐために、特別にここでキャッシュの処理を登録している。
51
53
 
52
- def perform_request(options, &block)
53
- payload = {operation: 'collect', args: options[:args]}
54
+ def perform_request(method_name, options, &block)
55
+ payload = {operation: 'collect', args: [method_name, options.slice(:max_id, :cursor, :super_operation)]}
54
56
  ::ActiveSupport::Notifications.instrument('collect.twitter_friendly', payload) { yield(payload) }
55
57
  end
56
58
  end
@@ -67,8 +69,8 @@ module TwitterFriendly
67
69
  if options[:recursive]
68
70
  do_request.call
69
71
  else
70
- TwitterFriendly::Caching::Instrumenter.start_processing(name, options)
71
- TwitterFriendly::Caching::Instrumenter.complete_processing(name, options, &do_request)
72
+ TwitterFriendly::CachingAndLogging::Instrumenter.start_processing(name, options)
73
+ TwitterFriendly::CachingAndLogging::Instrumenter.complete_processing(name, options, &do_request)
72
74
  end
73
75
  end
74
76
  end