simple-feed 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +7 -0
  2. data/.codeclimate.yml +30 -0
  3. data/.gitignore +16 -0
  4. data/.rspec +2 -0
  5. data/.rubocop.yml +1156 -0
  6. data/.travis.yml +14 -0
  7. data/.yardopts +3 -0
  8. data/Gemfile +4 -0
  9. data/Guardfile +18 -0
  10. data/LICENSE.txt +21 -0
  11. data/README.md +457 -0
  12. data/Rakefile +16 -0
  13. data/bin/console +14 -0
  14. data/bin/setup +8 -0
  15. data/examples/hash_provider_example.rb +24 -0
  16. data/examples/redis_provider_example.rb +28 -0
  17. data/examples/shared/provider_example.rb +66 -0
  18. data/lib/simple-feed.rb +1 -0
  19. data/lib/simple_feed.rb +1 -0
  20. data/lib/simplefeed.rb +61 -0
  21. data/lib/simplefeed/activity/base.rb +14 -0
  22. data/lib/simplefeed/activity/multi_user.rb +71 -0
  23. data/lib/simplefeed/activity/single_user.rb +70 -0
  24. data/lib/simplefeed/dsl.rb +38 -0
  25. data/lib/simplefeed/dsl/activities.rb +70 -0
  26. data/lib/simplefeed/dsl/formatter.rb +109 -0
  27. data/lib/simplefeed/event.rb +87 -0
  28. data/lib/simplefeed/feed.rb +78 -0
  29. data/lib/simplefeed/providers.rb +45 -0
  30. data/lib/simplefeed/providers/base/provider.rb +84 -0
  31. data/lib/simplefeed/providers/hash.rb +8 -0
  32. data/lib/simplefeed/providers/hash/paginator.rb +31 -0
  33. data/lib/simplefeed/providers/hash/provider.rb +169 -0
  34. data/lib/simplefeed/providers/proxy.rb +38 -0
  35. data/lib/simplefeed/providers/redis.rb +9 -0
  36. data/lib/simplefeed/providers/redis/boot_info.yml +99 -0
  37. data/lib/simplefeed/providers/redis/driver.rb +158 -0
  38. data/lib/simplefeed/providers/redis/provider.rb +255 -0
  39. data/lib/simplefeed/providers/redis/stats.rb +85 -0
  40. data/lib/simplefeed/providers/serialization/key.rb +82 -0
  41. data/lib/simplefeed/response.rb +77 -0
  42. data/lib/simplefeed/version.rb +3 -0
  43. data/man/running-the-example.png +0 -0
  44. data/man/sf-example.png +0 -0
  45. data/simple-feed.gemspec +44 -0
  46. metadata +333 -0
@@ -0,0 +1,169 @@
1
+ require 'base62-rb'
2
+ require 'hashie'
3
+ require 'set'
4
+ require 'knjrbfw'
5
+
6
+ require 'simplefeed/event'
7
+ require_relative 'paginator'
8
+ require_relative '../serialization/key'
9
+ require_relative '../base/provider'
10
+
11
+ module SimpleFeed
12
+ module Providers
13
+ module Hash
14
+ class Provider < ::SimpleFeed::Providers::Base::Provider
15
+ attr_accessor :h
16
+
17
+ include SimpleFeed::Providers::Hash::Paginator
18
+
19
+ def self.from_yaml(file)
20
+ self.new(YAML.load(File.read(file)))
21
+ end
22
+
23
+ def initialize(**opts)
24
+ self.h = {}
25
+ h.merge!(opts)
26
+ end
27
+
28
+ def store(user_ids:, value:, at: Time.now)
29
+ event = create_event(value, at)
30
+ with_response_batched(user_ids) do |key|
31
+ add_event(event, key)
32
+ end
33
+ end
34
+
35
+ def delete(user_ids:, value:, at: nil)
36
+ event = create_event(value, at)
37
+ with_response_batched(user_ids) do |key|
38
+ changed_activity_size?(key) do
39
+ __delete(key, event)
40
+ end
41
+ end
42
+ end
43
+
44
+ def delete_if(user_ids:, &block)
45
+ with_response_batched(user_ids) do |key|
46
+ activity(key).each do |event|
47
+ __delete(key, event) if yield(key.user_id, event)
48
+ end
49
+ end
50
+ end
51
+
52
+ def wipe(user_ids:)
53
+ with_response_batched(user_ids) do |key|
54
+ deleted = activity(key).size > 0
55
+ wipe_user_record(key)
56
+ deleted
57
+ end
58
+ end
59
+
60
+ def fetch(user_ids:)
61
+ with_response_batched(user_ids) do |key|
62
+ activity(key)
63
+ end
64
+ end
65
+
66
+ def paginate(user_ids:, page:, per_page: feed.per_page, **options)
67
+ reset_last_read(user_ids: user_ids) unless options[:peek]
68
+ with_response_batched(user_ids) do |key|
69
+ activity = activity(key)
70
+ (page && page > 0) ? activity[((page - 1) * per_page)...(page * per_page)] : activity
71
+ end
72
+ end
73
+
74
+ def reset_last_read(user_ids:, at: Time.now)
75
+ with_response_batched(user_ids) do |key|
76
+ user_record(key)[:last_read] = at
77
+ at
78
+ end
79
+ end
80
+
81
+ def total_count(user_ids:)
82
+ with_response_batched(user_ids) do |key|
83
+ activity(key).size
84
+ end
85
+ end
86
+
87
+ def unread_count(user_ids:)
88
+ with_response_batched(user_ids) do |key|
89
+ activity(key).count { |event| event.at > user_record(key).last_read.to_f }
90
+ end
91
+ end
92
+
93
+ def last_read(user_ids:)
94
+ with_response_batched(user_ids) do |key|
95
+ user_record(key).last_read
96
+ end
97
+ end
98
+
99
+ def total_memory_bytes
100
+ analyzer = Knj::Memory_analyzer::Object_size_counter.new(self.h)
101
+ analyzer.calculate_size
102
+ end
103
+
104
+ def total_users
105
+ self.h.size
106
+ end
107
+
108
+ private
109
+
110
+ #===================================================================
111
+ # Methods below operate on a single user only
112
+ #
113
+
114
+
115
+ def changed_activity_size?(key)
116
+ ua = activity(key)
117
+ size_before = ua.size
118
+ yield(key, ua)
119
+ size_after = activity(key).size
120
+ (size_before > size_after)
121
+ end
122
+
123
+ def create_user_record
124
+ Hashie::Mash.new(
125
+ { last_read: 0, activity: SortedSet.new }
126
+ )
127
+ end
128
+
129
+ def user_record(key)
130
+ h[key.data] ||= create_user_record
131
+ end
132
+
133
+ def wipe_user_record(key)
134
+ h[key.data] = create_user_record
135
+ end
136
+
137
+ def activity(key, event = nil)
138
+ user_record(key)[:activity] << event if event
139
+ user_record(key)[:activity].to_a
140
+ end
141
+
142
+ def add_event(event, key)
143
+ uas = user_record(key)[:activity]
144
+ if uas.include?(event)
145
+ false
146
+ else
147
+ uas << event.dup
148
+ if uas.size > feed.max_size
149
+ uas.delete(uas.to_a.last)
150
+ end
151
+ true
152
+ end
153
+ end
154
+
155
+ def __last_read(key, value = nil)
156
+ user_record(key)[:last_read]
157
+ end
158
+
159
+ def __delete(key, event)
160
+ user_record(key)[:activity].delete(event)
161
+ end
162
+
163
+ def create_event(*args, **opts)
164
+ ::SimpleFeed::Event.new(*args, **opts)
165
+ end
166
+ end
167
+ end
168
+ end
169
+ end
@@ -0,0 +1,38 @@
1
+ module SimpleFeed
2
+ module Providers
3
+ class Proxy
4
+ attr_accessor :provider
5
+
6
+ def self.from(definition)
7
+ if definition.is_a?(::Hash)
8
+ ::SimpleFeed.symbolize!(definition)
9
+ self.new(definition[:klass], *definition[:args], **definition[:opts])
10
+ else
11
+ self.new(definition)
12
+ end
13
+
14
+ end
15
+
16
+ def initialize(provider_or_klass, *args, **options)
17
+ if provider_or_klass.is_a?(::String)
18
+ self.provider = ::Object.const_get(provider_or_klass).new(*args, **options)
19
+ else
20
+ self.provider = provider_or_klass
21
+ end
22
+
23
+ SimpleFeed::Providers::REQUIRED_METHODS.each do |m|
24
+ raise ArgumentError, "Invalid provider #{provider.class}\nMethod '#{m}' is required." unless provider.respond_to?(m)
25
+ end
26
+ end
27
+
28
+ # Forward all other method calls to Provider
29
+ def method_missing(name, *args, &block)
30
+ if self.provider && provider.respond_to?(name)
31
+ self.provider.send(name, *args, &block)
32
+ else
33
+ super(name, *args, &block)
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,9 @@
1
+ module SimpleFeed
2
+ module Providers
3
+ module Redis
4
+ end
5
+ end
6
+ end
7
+
8
+ require_relative 'redis/driver'
9
+ require_relative 'redis/provider'
@@ -0,0 +1,99 @@
1
+ ---
2
+ redis_version: 3.2.5
3
+ redis_git_sha1: '00000000'
4
+ redis_git_dirty: '0'
5
+ redis_build_id: 9fe990583d8f1fbf
6
+ redis_mode: standalone
7
+ os: Darwin 16.1.0 x86_64
8
+ arch_bits: '64'
9
+ multiplexing_api: kqueue
10
+ gcc_version: 4.2.1
11
+ process_id: '12404'
12
+ run_id: de08831d36013df78346b72b3ffc3f1f45147780
13
+ tcp_port: '6379'
14
+ uptime_in_seconds: '26'
15
+ uptime_in_days: '0'
16
+ hz: '10'
17
+ lru_clock: '4352014'
18
+ executable: "/usr/local/opt/redis/bin/redis-server"
19
+ config_file: "/usr/local/etc/redis.conf"
20
+ connected_clients: '1'
21
+ client_longest_output_list: '0'
22
+ client_biggest_input_buf: '0'
23
+ blocked_clients: '0'
24
+ used_memory: '1008384'
25
+ used_memory_human: 984.75K
26
+ used_memory_rss: '2027520'
27
+ used_memory_rss_human: 1.93M
28
+ used_memory_peak: '1008384'
29
+ used_memory_peak_human: 984.75K
30
+ total_system_memory: '17179869184'
31
+ total_system_memory_human: 16.00G
32
+ used_memory_lua: '37888'
33
+ used_memory_lua_human: 37.00K
34
+ maxmemory: '0'
35
+ maxmemory_human: 0B
36
+ maxmemory_policy: noeviction
37
+ mem_fragmentation_ratio: '2.01'
38
+ mem_allocator: libc
39
+ loading: '0'
40
+ rdb_changes_since_last_save: '0'
41
+ rdb_bgsave_in_progress: '0'
42
+ rdb_last_save_time: '1480746996'
43
+ rdb_last_bgsave_status: ok
44
+ rdb_last_bgsave_time_sec: "-1"
45
+ rdb_current_bgsave_time_sec: "-1"
46
+ aof_enabled: '0'
47
+ aof_rewrite_in_progress: '0'
48
+ aof_rewrite_scheduled: '0'
49
+ aof_last_rewrite_time_sec: "-1"
50
+ aof_current_rewrite_time_sec: "-1"
51
+ aof_last_bgrewrite_status: ok
52
+ aof_last_write_status: ok
53
+ total_connections_received: '1'
54
+ total_commands_processed: '1'
55
+ instantaneous_ops_per_sec: '0'
56
+ total_net_input_bytes: '43'
57
+ total_net_output_bytes: '2168'
58
+ instantaneous_input_kbps: '0.00'
59
+ instantaneous_output_kbps: '0.00'
60
+ rejected_connections: '0'
61
+ sync_full: '0'
62
+ sync_partial_ok: '0'
63
+ sync_partial_err: '0'
64
+ expired_keys: '0'
65
+ evicted_keys: '0'
66
+ keyspace_hits: '0'
67
+ keyspace_misses: '0'
68
+ pubsub_channels: '0'
69
+ pubsub_patterns: '0'
70
+ latest_fork_usec: '0'
71
+ migrate_cached_sockets: '0'
72
+ role: master
73
+ connected_slaves: '0'
74
+ master_repl_offset: '0'
75
+ repl_backlog_active: '0'
76
+ repl_backlog_size: '1048576'
77
+ repl_backlog_first_byte_offset: '0'
78
+ repl_backlog_histlen: '0'
79
+ used_cpu_sys: '0.02'
80
+ used_cpu_user: '0.01'
81
+ used_cpu_sys_children: '0.00'
82
+ used_cpu_user_children: '0.00'
83
+ cluster_enabled: '0'
84
+ db0: keys=0,expires=0,avg_ttl=0
85
+ db1: keys=0,expires=0,avg_ttl=0
86
+ db2: keys=0,expires=0,avg_ttl=0
87
+ db3: keys=0,expires=0,avg_ttl=0
88
+ db4: keys=0,expires=0,avg_ttl=0
89
+ db5: keys=0,expires=0,avg_ttl=0
90
+ db6: keys=0,expires=0,avg_ttl=0
91
+ db7: keys=0,expires=0,avg_ttl=0
92
+ db8: keys=0,expires=0,avg_ttl=0
93
+ db9: keys=0,expires=0,avg_ttl=0
94
+ db10: keys=0,expires=0,avg_ttl=0
95
+ db11: keys=0,expires=0,avg_ttl=0
96
+ db12: keys=0,expires=0,avg_ttl=0
97
+ db13: keys=0,expires=0,avg_ttl=0
98
+ db14: keys=0,expires=0,avg_ttl=0
99
+ db15: keys=0,expires=0,avg_ttl=0
@@ -0,0 +1,158 @@
1
+ require 'redis'
2
+ require 'redis/connection/hiredis'
3
+ require 'connection_pool'
4
+ require 'colored2'
5
+ require 'hashie/mash'
6
+ require 'yaml'
7
+ require 'pp'
8
+
9
+ module SimpleFeed
10
+ module Providers
11
+ module Redis
12
+ @debug = ENV['REDIS_DEBUG']
13
+
14
+ def self.debug?
15
+ self.debug
16
+ end
17
+
18
+ class << self
19
+ attr_accessor :debug
20
+ end
21
+
22
+ module Driver
23
+ class Error < StandardError;
24
+ end
25
+
26
+ class LoggingRedis < Struct.new(:redis)
27
+ def method_missing(m, *args, &block)
28
+ if redis.respond_to?(m)
29
+ result = redis.send(m, *args, &block)
30
+ STDERR.printf "%40s %s\n", "#{m.to_s.upcase.bold.red}", "#{args.inspect.gsub(/[\[\]]/, '').magenta}"
31
+ result
32
+ else
33
+ super
34
+ end
35
+ end
36
+ end
37
+
38
+ def debug?
39
+ SimpleFeed::Providers::Redis.debug?
40
+ end
41
+
42
+ attr_accessor :pool
43
+
44
+ =begin
45
+
46
+ Various ways of defining a new Redis driver:
47
+
48
+ SimpleFeed::Redis::Driver.new(pool: ConnectionPool.new(size: 2) { Redis.new })
49
+ SimpleFeed::Redis::Driver.new(redis: -> { Redis.new }, pool_size: 2)
50
+ SimpleFeed::Redis::Driver.new(redis: Redis.new)
51
+ SimpleFeed::Redis::Driver.new(redis: { host: 'localhost', port: 6379, db: 1, timeout: 0.2 }, pool_size: 1)
52
+
53
+ =end
54
+ def initialize(**opts)
55
+ if opts[:pool] && opts[:pool].respond_to?(:with)
56
+ self.pool = opts[:pool]
57
+
58
+ elsif opts[:redis]
59
+ redis = opts[:redis]
60
+ redis_proc = nil
61
+
62
+ if redis.is_a?(::Hash)
63
+ redis_proc = -> { ::Redis.new(**opts[:redis]) }
64
+ elsif redis.is_a?(::Proc)
65
+ redis_proc = redis
66
+ elsif redis.is_a?(::Redis)
67
+ redis_proc = -> { redis }
68
+ end
69
+
70
+ if redis_proc
71
+ self.pool = ::ConnectionPool.new(size: (opts[:pool_size] || 2)) do
72
+ redis_proc.call
73
+ end
74
+ end
75
+ end
76
+
77
+ raise ArgumentError, "Unable to construct Redis connection from arguments: #{opts.inspect}" unless self.pool && self.pool.respond_to?(:with)
78
+ end
79
+
80
+ %i(set get incr decr setex expire del setnx exists zadd zrange).each do |method|
81
+ define_method(method) do |*args|
82
+ self.exec method, *args
83
+ end
84
+ end
85
+
86
+ alias_method :delete, :del
87
+ alias_method :rm, :del
88
+ alias_method :exists?, :exists
89
+
90
+ def exec(redis_method, *args, **opts, &block)
91
+ send_proc = redis_method if redis_method.respond_to?(:call)
92
+ send_proc ||= ->(redis) { redis.send(redis_method, *args, &block) }
93
+
94
+ if opts[:pipelined]
95
+ opts.delete :pipelined
96
+ with_pipelined { |redis| send_proc.call(redis) }
97
+ else
98
+ with_redis { |redis| send_proc.call(redis) }
99
+ end
100
+ end
101
+
102
+ class MockRedis
103
+ def method_missing(name, *args, &block)
104
+ puts "calling redis.#{name}(#{args.to_s.gsub(/[\[\]]/, '')}) { #{block ? block.call : nil} }"
105
+ end
106
+ end
107
+
108
+ def with_redis
109
+ with_retries do
110
+ pool.with do |redis|
111
+ yield(self.debug? ? LoggingRedis.new(redis) : redis)
112
+ end
113
+ end
114
+ end
115
+
116
+ def with_pipelined
117
+ with_retries do
118
+ with_redis do |redis|
119
+ redis.pipelined do
120
+ yield(redis)
121
+ end
122
+ end
123
+ end
124
+ end
125
+
126
+ def with_multi
127
+ with_retries do
128
+ with_redis do |redis|
129
+ redis.multi do
130
+ yield(redis)
131
+ end
132
+ end
133
+ end
134
+ end
135
+
136
+ def with_retries(tries = 3)
137
+ yield(tries)
138
+ rescue Errno::EINVAL => e
139
+ on_error e
140
+ rescue ::Redis::BaseConnectionError => e
141
+ if (tries -= 1) > 0
142
+ sleep rand(0..0.01)
143
+ retry
144
+ else
145
+ on_error e
146
+ end
147
+ rescue ::Redis::CommandError => e
148
+ (e.message =~ /loading/i || e.message =~ /connection/i) ? on_error(e) : raise(e)
149
+ end
150
+
151
+ def on_error(e)
152
+ raise Error.new(e)
153
+ end
154
+
155
+ end
156
+ end
157
+ end
158
+ end