twitter_friendly 0.2.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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