{"id":13484365,"url":"https://github.com/kigster/simple-feed","last_synced_at":"2025-09-07T08:06:14.712Z","repository":{"id":13432435,"uuid":"74417105","full_name":"kigster/simple-feed","owner":"kigster","description":"This gem implements a flexible time-ordered activity feeds commonly used within social networking applications. As events occur, they are pushed into the Feed and distributed to all users that need to see the event. Upon the user visiting their \"feed page\", a pre-populated ordered list of events is returned by the library. ","archived":false,"fork":false,"pushed_at":"2024-07-23T16:06:18.000Z","size":4684,"stargazers_count":334,"open_issues_count":4,"forks_count":30,"subscribers_count":7,"default_branch":"main","last_synced_at":"2025-08-30T14:47:10.062Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/kigster.png","metadata":{"files":{"readme":"README.adoc","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2016-11-22T00:05:28.000Z","updated_at":"2025-05-29T09:11:33.000Z","dependencies_parsed_at":"2024-10-30T18:41:31.940Z","dependency_job_id":null,"html_url":"https://github.com/kigster/simple-feed","commit_stats":null,"previous_names":[],"tags_count":19,"template":false,"template_full_name":null,"purl":"pkg:github/kigster/simple-feed","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kigster%2Fsimple-feed","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kigster%2Fsimple-feed/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kigster%2Fsimple-feed/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kigster%2Fsimple-feed/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kigster","download_url":"https://codeload.github.com/kigster/simple-feed/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kigster%2Fsimple-feed/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":274010104,"owners_count":25206764,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-09-07T02:00:09.463Z","response_time":67,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-07-31T17:01:23.099Z","updated_at":"2025-09-07T08:06:14.654Z","avatar_url":"https://github.com/kigster.png","language":"Ruby","readme":"= SimpleFeed\n:doctype: book\n:toc:\n:toclevels: 5\n:sectnums:\n:icons: font\n\n====\n\nhttps://liberapay.com/kigster/donate[image:https://liberapay.com/assets/widgets/donate.svg[Donate using Liberapay,height=30]]\n\nhttps://liberapay.com/kigster/donate[image:https://img.shields.io/liberapay/goal/kigster.svg?logo=liberapay[https://img.shields.io/liberapay/goal/kigster,height=50]]\n====\n\n== Scalable, Easy to Use Activity (aka \"News\") Feed\n\n=== What is this gem? — A Quick Summary\n\nNOTE: This gem is the Redis-backed backend implementation for a reverse chronological order news feed for a typical social network similar in some ways to Twitter: where users have followers, but also topics or a categories can have followers. When an event is posted by a user, it is also pushed to various individual feeds, and the gem then offers `O(1)` speed pagination for reading any page of the feed. The limitation is that the number of feed items must be capped (the default is 4000 items). The implementation can be scaled horizontally by sharding Redis backend, which can be implemented using a Redis Proxy layer such as https://github.com/twitter/twemproxy[Twemproxy] or https://github.com/envoyproxy/envoy/issues/19436[Envoy]).\n\n=== Build \u0026 Gem Status\n\nimage:https://img.shields.io/badge/license-MIT-blue.svg[MIT licensed,link=https://github.com/kigster/simple-feed/main/LICENSE.txt]\nimage:https://github.com/kigster/simple-feed/actions/workflows/ruby.yml/badge.svg[RSpec,link=https://github.com/kigster/simple-feed/actions/workflows/ruby.yml]\nimage:https://github.com/kigster/simple-feed/actions/workflows/rubocop.yml/badge.svg[Rubocop,link=https://github.com/kigster/simple-feed/actions/workflows/rubocop.yml]\n\nimage:https://img.shields.io/gem/v/simple-feed.svg[Gem Version,link=https://rubygems.org/gems/simple-feed]\nimage:https://codecov.io/gh/kigster/simple-feed/branch/master/graph/badge.svg?token=RvDkGqoahz[Coverage,link=https://codecov.io/gh/kigster/simple-feed]\n\n=== Test Coverage Map\n\nimage:https://codecov.io/gh/kigster/simple-feed/graphs/sunburst.svg?token=RvDkGqoahz[Coverage Map,link=https://codecov.io/gh/kigster/simple-feed/branch/master]\n\nIMPORTANT: Please read the (somewhat outdated) blog post http://kig.re/2017/02/19/feeding-frenzy-with-simple-feed-activity-feed-ruby-gem.html[Feeding Frenzy with SimpleFeed] launching this library. Please leave comments or questions in the discussion thread at the bottom of that post. Thanks!\n\nIf you like to see this project grow, your donation of any amount is much appreciated.\n\nimage::https://www.paypalobjects.com/en_US/i/btn/btn_donate_SM.gif[Donate,link=https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick\u0026hosted_button_id=FSFYYNEQ8RKWU]\n\nThis is a fast, pure-ruby implementation of an activity feed concept commonly used in social networking applications. The implementation is optimized for *read-time performance* and high concurrency (lots of users), and can be extended with custom backend providers. One data provider come bundled: the production-ready Redis provider.\n\n*Important Notes and Acknowledgements:*\n\n* SimpleFeed _does not depend on Ruby on Rails_ and is a *pure-ruby* implementation\n* SimpleFeed requires MRI Ruby 2.3 or later\n* SimpleFeed is currently live in production\n* SimpleFeed can use https://github.com/redis/hiredis[hiredis] if you install it separately and load it before loading the gem.\n* SimpleFeed is open source thanks to the generosity of *http://simbi.com[Simbi, Inc]*.\n\n== Features\n\nSimpleFeed is a Ruby Library that can be plugged into any application to power a fast, Redis-based activity feed implementation so common on social networking sites. SimpleFeed offers the following features:\n\n * Modelled after graph-relationships similar to those on Twitter (bi-directional independent follow relationships):\n ** Feed maintains a reverse-chronological order for heterogeneous events for each user.\n ** It offers a constant time lookup for user's feed, avoiding complex SQL joins to render it.\n ** An API to read/paginate the feed for a given user\n ** As well as to *query the total unread items in the feed* since it was last read by the user (typically shown on App icons).\n * Scalable and well performing Redis-based activity feed —\n ** Scales to millions of users (will need to use Twemproxy to shard across several Redis instances)\n ** Stores a fixed number of events for each unique \"user\" — the default is 1000. When the feed reaches 1001 events, the oldest event is offloaded from the activity.\n * Implementation properties:\n ** Fully thread-safe implementation, writing events can be done in eg. Sidekiq.\n ** Zero assumptions about what you are storing: the \"data\" is just a string. Serialize it with JSON, Marshall, YAML, or whatever.\n ** You can create as many different types of feeds per application as you like (no Ruby Singletons used).\n ** Customize mapping from `user_id` to the activity id based on your business logic (more on this later).\n\n=== Publishing Events\n\nPushing events to the feed requires the following:\n\n * An `Event` consisting of:\n ** String `data` that, most commonly, is a foreign key to a database table, but can really be anything you like.\n ** Float `at` (typically, the timestamp, but can be any `float` number)\n * One or more user IDs, or event consumers: basically — who should see the event being published in their feed.\n\nYou publish an event by choosing a set of users whose feed should be updated. For example, were you re-implementing Twitter, your array of `user_ids` when publishing an event would be all followers of the Tweet's author. While the `data` would probably be the Tweet ID.\n\n\nNOTE: Publishing an event to the feeds of N users is roughly a O(N * log(N)) operation\n\n=== Consuming Events (Reading / Rendering the Feed)\n\nYou can fetch the chronologically ordered events for a particular user, using:\n\n * Methods on the `activity` such as `paginate`, `fetch`.\n\n ** Reading feed for one user (or one type of user) is a `O(1)` operation\n\n * For each activity (user) you can fetch the `total_count` and the `unread_count` — the number of total and new items in the feed, where `unread_count` is computed since the user last reset their `read status`.\n\n ** Note: `total_count` can never exceed the maximum size of the feed that you configured. The default is 1000 items.\n\n ** The `last_read` timestamp can be automatically reset when the user is shown the feed via `paginate` method (whether or not its reset is controlled via a method argument).\n\n=== Modifying User's Feed\n\nFor any given user, you can:\n\n * Wipe their feed with `wipe`\n\n * Selectively remove items from the feed with `delete_if`.\n ** For instance, if a user un-follows someone they shouldn't see their events anymore, so you'd have to call `delete_if` and remove any events published by the unfollowed user.\n\n=== Aggregating Events\n\nThis is a feature planned for future versions.\n\nHelp us much appreciated, even if you are not a developer, but have a clear idea about how it should work.\n\n== Commercial \u0026 Enterprise Support\n\nCommercial Support plans are available for SimpleFeed through author's https://reinvent.one[ReinventONE Inc] consulting company. Please reach out to kig AT reinvent.one for more information.\n\n== Usage\n\n=== Example\n\nPlease read the additional documentation, including the examples, on the https://github.com/kigster/simple-feed/wiki[project's Github Wiki].\n\nBelow is a screen shot of an actual activity feed powered by this library.\n\nimage::https://raw.githubusercontent.com/kigster/simple-feed/main/man/activity-feed-action.png[usage]\n\n=== Providers\n\nA key concept to understanding SimpleFeed gem, is that of a _provider_, which is effectively a persistence implementation for the events belonging to each user.\n\nOne providers are supplied with this gem: the production-ready `:redis` provider, which uses the https://redislabs.com/ebook/redis-in-action/part-2-core-concepts-2/chapter-3-commands-in-redis/3-5-sorted-sets[sorted set Redis data type] to store and fetch the events, scored by time (but not necessarily).\n\nYou initialize a provider by using the `SimpleFeed.provider([Symbol])` method.\n\n=== Configuration\n\nBelow we configure a feed called `:newsfeed`, which in this example will be populated with the various events coming from the followers.\n\n[source,ruby]\n----\nrequire 'simplefeed'\n\n# Let's define a Redis-based feed, and wrap Redis in a in a ConnectionPool.\n\nSimpleFeed.define(:newsfeed) do |f|\n  f.provider   = SimpleFeed.provider(:redis,\n                                      redis: -\u003e { ::Redis.new },\n                                      pool_size: 10)\n  f.per_page   = 50     # default page size\n  f.batch_size = 10     # default batch size\n  f.namespace  = 'nf'   # only needed if you use the same redis for more than one feed\nend\n----\n\nAfter the feed is defined, the gem creates a similarly named method under the `SimpleFeed` namespace to access the feed. For example, given a name such as `:newsfeed` the following are all valid ways of accessing the feed:\n\n* `SimpleFeed.newsfeed`\n* `SimpleFeed.get(:newsfeed)`\n\nYou can also get a full list of currently defined feeds with `SimpleFeed.feed_names` method.\n\n=== Reading from and writing to the feed\n\nFor the impatient, here is a quick way to get started with the `SimpleFeed`.\n\n[source,ruby]\n----\n# Let's use the feed we defined earlier and create activity for all followers of the current user\npublish_activity = SimpleFeed.newsfeed.activity(@current_user.followers.map(\u0026:id))\n\n# Store directly the value and the optional time stamp\npublish_activity.store(value: 'hello', at: Time.now)\n# =\u003e true  # indicates that value 'hello' was not yet in the feed (all events must be unique)\n\n# Or, using the event form:\npublish_activity.store(event: SimpleFeed::Event.new('good bye', Time.now))\n# =\u003e true\n----\n\nAs we've added the two events for these users, we can now read them back, sorted by\nthe time and paginated:\n\n[source,ruby]\n----\n# Let's grab the first follower\nuser_activity = SimpleFeed.newsfeed.activity(@current_user.followers.first.id)\n\n# Now we can paginate the events, while resetting this user's last-read timestamp:\nuser_activity.paginate(page: 1, reset_last_read: true)\n# [\n#     [0] #\u003cSimpleFeed::Event: value=hello, at=1480475294.0579991\u003e,\n#     [1] #\u003cSimpleFeed::Event: value=good bye, at=1480472342.8979871\u003e,\n# ]\n----\n\nIMPORTANT: Note that we stored the activity by passing an array of users, but read the activity for just one user. This is how you'd use SimpleFeed most of the time, with the exception of the alternative mapping described below.\n\n=== User IDs\n\nIn the previous section you saw the examples of publishing events to many feeds, and then reading the activity for a given user.\n\nSimpleFeed supports user IDs that are either numeric (integer) or string-based (eg, UUID). Numeric IDs are best for simplest cases, and are the most compact. String keys offer the most flexibility.\n\n==== Activity Keys\n\nIn the next section we'll talk about generating `keys` from user_ids. We mean — Redis Hash keys that uniquely map a user (or a set of users) to the activity feed they should see.\n\nThere are up to two keys that are computed depending on the situation:\n\n * `data_key` is used to store the actual feed events\n * `meta_key` is used to store user's `last_read` status\n\n==== Partitioning Schema\n\nNOTE: This feature is only available in **SimpleFeed Version 3+**.\n\nYou can take advantage of string user IDs for situations where your feed requires keys to be composite for instance. Just remember that SimpleFeed does not care about what's in your user ID, and even what you call \"a user\". It's convenient to think of the activities in terms of users, because typically each user has a unique feed that only they see.\n\nBut you can just as easily use zip code as the unique activity ID, and create one feed of events per geographical location, that all folks living in that zip code share. But what about other countries?\n\nNow you use partitioning scheme: make the \"user_id\" argument a combination `iso_country_code.postal_code`, eg for San Francisco, you'd use `us.94107`, but for Australia you could use, eg `au.3148`.\n\n==== Relationship between an Activity and a User\n\n===== One to One\n\nIn the most common case, you will have one activity per user.\n\nFor instance, in the Twitter example, each Twitter user has a unique tweeter feed that only they see.\n\nThe events are published when someone posts a tweet, to the array of all users that follow the Tweet author.\n\n===== One to Many\n\nHowever, SimpleFeed supports one additional use-case, where you might have one activity shared among many users.\n\nImagine a service that notifies residents of important announcements based on user's zip code of residence.\n\nWe want this feed to work as follows:\n\n * All users that share a zip-code should see the same exact feed.\n * However, all users should never share the individual's `last_read` status: so if two people read the same activity from the same zip code, their `unread_count` should change independently.\n\nIn terms of the activity keys, this means:\n\n * `data_key` should be based on the zip-code of each user, and be one to many with users.\n * `meta_key` should be based on the user ID as we want it to be 1-1 with users.\n\nTo support this use-case, SimpleFeed supports two optional transformer lambdas that can be applied to each user object when computing their activity feed hash key:\n\n[source,ruby]\n----\nSimpleFeed.define(:zipcode_alerts) do |f|\n  f.provider   = SimpleFeed.provider(:redis, redis: -\u003e { ::Redis.new }, pool_size: 10)\n  f.namespace  = 'zc'\n  f.data_key_transformer = -\u003e(user) { user.zip_code }  # actual feed data is stored once per zip code\n  f.meta_key_transformer = -\u003e(user) { user.id }        # last_read status is stored once per user\nend\n----\n\nWhen you publish events into this feed, you would need to provide `User` objects that all respond to `.zip_code` method (based on the above configuration). Since the data is only defined by Zip Code, you probably don't want to be publishing it via a giant array of users. Most likely, you'll want to publish event based on the zip code, and consume them based on the user ID.\n\nTo support this user-case, let's modify our transformer lambda (only the `data` one) as follows — so that it can support both the consuming read by a user case, and the publishing a feed by zip code case:\n\nAlternatively, you could do something like this:\n\n[source,ruby]\n----\n  f.data_key_transformer = -\u003e(entity) do\n    case entity\n      when User\n        entity.zip_code.to_i\n      when String # UUIDs\n        User.find(entity)\u0026.zip_code.to_i\n      when ZipCode, Numeric\n        entity.to_i\n      else\n        raise ArgumentError, \"Invalid type #{entity.class.name}\"\n    end\n  end\n----\n\nJust make sure that your users always have `.zip_code` defined, and that `ZipCode.new(94107).to_i` returns exactly the same thing as `@user.zip_code.to_i` or your users won't see the feeds they are supposed to see.\n\n=== The Two Forms of the Feed API\n\nThe feed API is offered in two forms:\n\n. single-user form, and\n. a batch (multi-user) form.\n\nThe method names and signatures are the same. The only difference is in what the methods return:\n\n. In the single user case, the return of, say, `#total_count` is an `Integer` value representing the total count for this user.\n. In the multi-user case, the return is a `SimpleFeed::Response` instance, that can be thought of as a `Hash`, that has the user IDs as the keys, and return results for each user as a value.\n\nPlease see further below the details about the \u003c\u003cbatch-api,Batch API\u003e\u003e.\n\n[discrete]\n\n===== Single-User API\n\nIn the examples below we show responses based on a single-user usage. As previously mentioned, the multi-user usage is the same, except what the response values are, and is discussed further down below.\n\nLet's take a look at a ruby session, which demonstrates return values of the feed operations for a single user:\n\n[source,ruby]\n----\nrequire 'simplefeed'\n\n# Define the feed using Redis provider, which uses\n# SortedSet to keep user's events sorted.\nSimpleFeed.define(:followers) do |f|\n  f.provider = SimpleFeed.provider(:redis)\n  f.per_page = 50\n  f.per_page = 2\nend\n\n# Let's get the Activity instance that wraps this\nactivity = SimpleFeed.followers.activity(user_id)         # =\u003e [... complex object removed for brevity ]\n\n# let's clear out this feed to ensure it's empty\nactivity.wipe                                             # =\u003e true\n\n# Let's verify that the counts for this feed are at zero\nactivity.total_count                                      # =\u003e 0\nactivity.unread_count                                     # =\u003e 0\n\n# Store some events\nactivity.store(value: 'hello')                            # =\u003e true\nactivity.store(value: 'goodbye', at: Time.now - 20)       # =\u003e true\nactivity.unread_count                                     # =\u003e 2\n\n# Now we can paginate the events, while resetting this user's last-read timestamp:\nactivity.paginate(page: 1, reset_last_read: true)\n# [\n#     [0] #\u003cSimpleFeed::Event: value=good bye, at=1480475294.0579991\u003e,\n#     [1] #\u003cSimpleFeed::Event: value=hello, at=1480475294.057138\u003e\n# ]\n# Now the unread_count should return 0 since the user just \"viewed\" the feed.\nactivity.unread_count                                     # =\u003e 0\nactivity.delete(value: 'hello')                           # =\u003e true\n# the next method yields to a passed in block for each event in the user's feed, and deletes\n# all events for which the block returns true. The return of this call is the\n# array of all events that have been deleted for this user.\nactivity.delete_if do |event, user_id|\n  event.value =~ /good/\nend\n# =\u003e [\n#     [0] #\u003cSimpleFeed::Event: value=good bye, at=1480475294.0579991\u003e\n# ]\nactivity.total_count                                      # =\u003e 0\n----\n\nYou can fetch all items (optionally filtered by time) in the feed using `#fetch`,\n`#paginate` and reset the `last_read` timestamp by passing the `reset_last_read: true` as a parameter.\n\n+++\u003ca name=\"batch-api\"\u003e++++++\u003c/a\u003e+++\n\n[discrete]\n===== Batch (Multi-User) API\n\nThis API should be used when dealing with an array of users (or, in the\nfuture, a Proc or an ActiveRecord relation).\n\n____\nThere are several reasons why this API should be preferred for\noperations that perform a similar action across a range of users:\n_various provider implementations can be heavily optimized for\nconcurrency, and performance_.\n\nThe Redis Provider, for example, uses a notion of `pipelining` to send\nupdates for different users asynchronously and concurrently.\n____\n\nMulti-user operations return a `SimpleFeed::Response` object, which can\nbe used as a hash (keyed on user_id) to fetch the result of a given\nuser.\n\n[source,ruby]\n----\n# Using the Feed API with, eg #find_in_batches\n@event_producer.followers.find_in_batches do |group|\n\n  # Convert a group to the array of IDs and get ready to store\n  activity = SimpleFeed.get(:followers).activity(group.map(\u0026:id))\n  activity.store(value: \"#{@event_producer.name} liked an article\")\n\n  # =\u003e [Response] { user_id1 =\u003e [Boolean], user_id2 =\u003e [Boolean]... }\n  # true if the value was stored, false if it wasn't.\nend\n----\n\n[discrete]\n===== Activity Feed DSL (Domain-Specific Language)\n\nThe library offers a convenient DSL for adding feed functionality into\nyour current scope.\n\nTo use the module, just include `SimpleFeed::DSL` where needed, which\nexports just one primary method `#with_activity`. You call this method\nand pass an activity object created for a set of users (or a single\nuser), like so:\n\n[source,ruby]\n----\nrequire 'simplefeed/dsl'\ninclude SimpleFeed::DSL\n\nfeed = SimpleFeed.newsfeed\nactivity = feed.activity(current_user.id)\ndata_to_store = %w(France Germany England)\n\ndef report(value)\n  puts value\nend\n\nwith_activity(activity, countries: data_to_store) do\n  # we can use countries as a variable because it was passed above in **opts\n  countries.each do |country|\n    # we can call #store without a receiver because the block is passed to\n    # instance_eval\n    store(value: country) { |result| report(result ? 'success' : 'failure') }\n    # we can call #report inside the proc because it is evaluated in the\n    # outside context of the #with_activity\n\n    # now let's print a color ASCII dump of the entire feed for this user:\n    color_dump\n  end\n  printf \"Activity counts are: %d unread of %d total\\n\", unread_count, total_count\nend\n----\n\nThe DSL context has access to two additional methods:\n\n* `#event(value, at)` returns a fully constructed `SimpleFeed::Event` instance\n* `#color_dump` prints to STDOUT the ASCII text dump of the current user's activities (events), as well as the counts and the `last_read` shown visually on the time line.\n\n[discrete]\n===== `#color_dump`\n\nBelow is an example output of `color_dump` method, which is intended for the debugging purposes.\n\nimage::https://raw.githubusercontent.com/kigster/simple-feed/main/man/sf-color-dump.png[title=#color_dump method output, width=659, link=https://raw.githubusercontent.com/kigster/simple-feed/main/man/sf-color-dump.png]\n\n+++\u003ca name=\"api\"\u003e++++++\u003c/a\u003e+++\n\n== Complete API\n\nFor completeness sake we'll show the multi-user API responses only. For a single-user use-case the response is typically a scalar, and the input is a singular `user_id`, not an array of ids.\n\n[discrete]\n==== Multi-User (Batch) API\n\nEach API call at this level expects an array of user IDs, therefore the\nreturn value is an object, `SimpleFeed::Response`, containing individual\nresponses for each user, accessible via `response[user_id]` method.\n\n[source,ruby]\n----\n@multi = SimpleFeed.get(:feed_name).activity(User.active.map(\u0026:id))\n\n@multi.store(value:, at:)\n@multi.store(event:)\n# =\u003e [Response] { user_id =\u003e [Boolean], ... } true if the value was stored, false if it wasn't.\n\n@multi.delete(value:, at:)\n@multi.delete(event:)\n# =\u003e [Response] { user_id =\u003e [Boolean], ... } true if the value was removed, false if it didn't exist\n\n@multi.delete_if do |event, user_id|\n  # if the block returns true, the event is deleted and returned\nend\n# =\u003e [Response] { user_id =\u003e [deleted_event1, deleted_event2, ...], ... }\n\n# Wipe the feed for a given user(s)\n@multi.wipe\n# =\u003e [Response] { user_id =\u003e [Boolean], ... } true if user activity was found and deleted, false otherwise\n\n# Return a paginated list of all items, optionally with the total count of items\n@multi.paginate(page: 1,\n                per_page: @multi.feed.per_page,\n                with_total: false,\n                reset_last_read: false)\n# =\u003e [Response] { user_id =\u003e [Array]\u003cEvent\u003e, ... }\n# Options:\n#   reset_last_read: false — reset last read to Time.now (true), or the provided timestamp\n#   with_total: true — returns a hash for each user_id:\n#        =\u003e [Response] { user_id =\u003e { events: Array\u003cEvent\u003e, total_count: 3 }, ... }\n\n# Return un-paginated list of all items, optionally filtered\n@multi.fetch(since: nil, reset_last_read: false)\n# =\u003e [Response] { user_id =\u003e [Array]\u003cEvent\u003e, ... }\n# Options:\n#   reset_last_read: false — reset last read to Time.now (true), or the provided timestamp\n#   since: \u003ctimestamp\u003e — if provided, returns all items posted since then\n#   since: :last_read — if provided, returns all unread items and resets +last_read+\n\n@multi.reset_last_read\n# =\u003e [Response] { user_id =\u003e [Time] last_read, ... }\n\n@multi.total_count\n# =\u003e [Response] { user_id =\u003e [Integer, String] total_count, ... }\n\n@multi.unread_count\n# =\u003e [Response] { user_id =\u003e [Integer, String] unread_count, ... }\n\n@multi.last_read\n# =\u003e [Response] { user_id =\u003e [Time] last_read, ... }\n----\n\n== Providers\n\nAs we've discussed above, a provider is an underlying persistence mechanism implementation.\n\nIt is the intention of this gem that:\n\n* it should be easy to write new providers\n* it should be easy to swap out providers\n\nOne provider is included with this gem:\n\n=== `SimpleFeed::Providers::Redis::Provider`\n\nRedis Provider is a production-ready persistence adapter that uses the https://redislabs.com/ebook/redis-in-action/part-2-core-concepts-2/chapter-3-commands-in-redis/3-5-sorted-sets[sorted set Redis data type].\n\nThis provider is optimized for large writes and can use either a single Redis instance for all users of your application, or any number of Redis https://en.wikipedia.org/wiki/Shard_(database_architecture)[shards] by using a https://github.com/twitter/twemproxy[_Twemproxy_] in front of the Redis shards.\n\nIf you set environment variable `REDIS_DEBUG` to `true` and run the example (see below) you will see every operation redis performs. This could be useful in debugging an issue or submitting a bug report.\n\n== Running the Examples and Specs\n\nSource code for the gem contains the `examples` folder with an example file that can be used to test out the providers, and see what they do under the hood.\n\nBoth the specs and the example requires a local redis instance to be available.\n\nTo run it, checkout the source of the library, and then:\n\n----\ngit clone https://github.com/kigster/simple-feed.git\ncd simple-feed\n\n# on OSX with HomeBrew:\nbrew install redis\nbrew services start redis\n\n# check that your redis is up:\nredis-cli info\n\n# install bundler and other dependencies\ngem install bundler --version 2.1.4\nbundle install\nbundle exec rspec  # make sure tests are passing\n\n# run the example:\nruby examples/redis_provider_example.rb\n----\n\nThe above command will help you download, setup all dependencies, and run the examples for a single user:\n\nimage::https://raw.githubusercontent.com/kigster/simple-feed/main/man/running-example.png[title=Running Redis Example in a Terminal, width=663, link=https://raw.githubusercontent.com/kigster/simple-feed/main/man/running-example.png]\n\nIf you set `REDIS_DEBUG` variable prior to running the example, you will be able to see every single Redis command executed as the example works its way through. Below is a sample output:\n\nimage::https://raw.githubusercontent.com/kigster/simple-feed/main/man/running-example-redis-debug.png[title=Running Redis Example with REDIS_DEBUG set, width=918, link=https://raw.githubusercontent.com/kigster/simple-feed/main/man/running-example-redis-debug.png]\n\n=== Generating Ruby API Documentation\n\n[source,bash]\n----\nrake doc\n----\n\nThis should use Yard to generate the documentation, and open your browser once it's finished.\n\n=== Installation\n\nAdd this line to your application's Gemfile:\n\n[source,ruby]\n----\ngem 'simple-feed'\n----\n\nAnd then execute:\n\n $ bundle\n\nOr install it yourself as:\n\n $ gem install simple-feed\n\n=== Development\n\nAfter checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.\n\nTo install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to https://rubygems.org[rubygems.org].\n\n=== Contributing\n\nBug reports and pull requests are welcome on GitHub at https://github.com/kigster/simple-feed\n\n=== License\n\nThe gem is available as open source under the terms of the http://opensource.org/licenses/MIT[MIT License].\n\nimage:https://app.fossa.com/api/projects/git%2Bgithub.com%2Fkigster%2Fsimple-feed.svg?type=large[FOSSA Scan Status, link=https://app.fossa.com/projects/git%2Bgithub.com%2Fkigster%2Fsimple-feed?ref=badge_large]\n\n=== Acknowledgements\n\n* This project is conceived and sponsored by https://simbi.com[Simbi, Inc.].\n* Author's personal experience at https://wanelo.com[Wanelo, Inc.] has served as an inspiration.\n","funding_links":["https://liberapay.com/kigster/donate","https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick\u0026hosted_button_id=FSFYYNEQ8RKWU"],"categories":["Ruby","ORM/ODM Extensions"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkigster%2Fsimple-feed","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkigster%2Fsimple-feed","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkigster%2Fsimple-feed/lists"}