[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"project-8047":3},{"id":4,"name":5,"fullName":6,"owner":7,"repo":5,"description":8,"homepage":9,"htmlUrl":10,"language":11,"languages":10,"totalLinesOfCode":10,"stars":12,"forks":13,"watchers":14,"openIssues":15,"contributorsCount":16,"subscribersCount":16,"size":16,"stars1d":16,"stars7d":16,"stars30d":16,"stars90d":16,"forks30d":16,"starsTrendScore":16,"compositeScore":17,"rankGlobal":10,"rankLanguage":10,"license":18,"archived":19,"fork":19,"defaultBranch":20,"hasWiki":21,"hasPages":19,"topics":22,"createdAt":10,"pushedAt":10,"updatedAt":23,"readmeContent":24,"aiSummary":25,"trendingCount":16,"starSnapshotCount":16,"syncStatus":26,"lastSyncTime":27,"discoverSource":28},8047,"redis-objects","nateware\u002Fredis-objects","nateware","Map Redis types directly to Ruby objects","",null,"Ruby",2095,231,37,11,0,29.1,"Artistic License 2.0",false,"master",true,[],"2026-06-12 02:01:48","# Redis::Objects - Map Redis types directly to Ruby objects\n\n[![Build Status](https:\u002F\u002Fapp.travis-ci.com\u002Fnateware\u002Fredis-objects.svg?branch=master)](https:\u002F\u002Ftravis-ci.com\u002Fgithub\u002Fnateware\u002Fredis-objects)\n[![Code Coverage](https:\u002F\u002Fcodecov.io\u002Fgh\u002Fnateware\u002Fredis-objects\u002Fbranch\u002Fmaster\u002Fgraph\u002Fbadge.svg)](https:\u002F\u002Fcodecov.io\u002Fgh\u002Fnateware\u002Fredis-objects)\n[![Donate](https:\u002F\u002Fwww.paypalobjects.com\u002Fen_US\u002Fi\u002Fbtn\u002Fbtn_donate_SM.gif)](https:\u002F\u002Fwww.paypal.com\u002Fcgi-bin\u002Fwebscr?cmd=_s-xclick&hosted_button_id=MJF7JU5M7F8VL)\n\n## Important 2.0 changes\n\nSeveral longstanding bugs have been addressed in this release. However, this may require some\ncode changes as part of the upgrade.\n\n### Renaming of `lock` Method\n\nThe `lock` method that collided with `ActiveRecord::Base` has been renamed `redis_lock`.\nThis means your classes need to be updated to call `redis_lock` instead:\n\n```ruby\nclass YouClassNameHere \u003C ActiveRecord::Base\n  include Redis::Objects\n  redis_lock :mylock  # formerly just \"lock\"\nend\n```\n\nFor more details on the issue and fix refer to [#196](https:\u002F\u002Fgithub.com\u002Fnateware\u002Fredis-objects\u002Fissues\u002F196).\n\n### New key naming method\n\nA new method to determine the internal key naming scheme has been added to fix a\nlongstanding bug. (Refer to [#213](https:\u002F\u002Fgithub.com\u002Fnateware\u002Fredis-objects\u002Fissues\u002F231))\nBy default, backwards compatibility is maintained, but this means that in some cases,\nNested classes (for example `Dog::Behavior` and `Cat::Behavior`) would have key names\nthat collide (they would both start with `Behavior` as anything before `::` is stripped).\n\nTo ensure names are unique, you can set `Redis::Objects.prefix_style = :modern` after\nyou load the module. By default, it is set to `Redis::Objects.prefix_style = :legacy`.\n\n### Migrating old keys\n\nIf your Redis::Object subclasses are nested such as `Dog::Behavior` and `Cat::Behavior`,\nthen you should upgrade them using the below proceedure. (only needed once)\n\nCreate a script like this:\n\n```ruby\nclass Dog::Behavior \u003C ActiveRecord::Base\n  include Redis::Objects\n  # ... your relevant redis_object definitions here (counters\u002Fsets) ...\nend\n\nDog::Behavior.migrate_redis_legacy_keys\n```\n\nYou need to find a time when you can temporarily pause writes to your redis server\nso that you can run that script. It uses `redis.scan` internally so it should be able to\nhandle a high number of keys. For large data sets, it could take a while.\n\nAfter migrating all of your redis keys, update `Redis::Objects.prefix_style = :modern` to\nstart using the new keys.\n\n**Note: Your existing data in Redis will not be accessible after running `migrate_redis_legacy_keys`\nuntil the `prefix_style` has been changed.**\n\n---\n\n# Overview\n\nThis is **not** an ORM ([Object-Relational Mapping](https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FObject%E2%80%93relational_mapping)).\nPeople that are wrapping ORM’s around Redis are missing the point.\n\nThe killer feature of Redis is that it allows you to perform _atomic_ operations\non _individual_ data structures, like counters, lists, and sets. The **atomic** part is HUGE.\nUsing an ORM wrapper that retrieves a \"record\", updates values, then sends those values back,\n_removes_ the atomicity, and thus the major advantage of Redis. Just use MySQL, k?\n\nThis gem provides a Rubyish interface to Redis, by mapping [Redis data types](http:\u002F\u002Fredis.io\u002Fcommands)\nto Ruby objects, via a thin layer over the `redis` gem. It offers several advantages\nover the lower-level redis-rb API:\n\n1. Easy to integrate directly with existing ORMs - ActiveRecord, DataMapper, etc. Add counters to your model!\n2. Complex data structures are automatically Marshaled (if you set :marshal => true)\n3. Integers are returned as integers, rather than '17'\n4. Higher-level types are provided, such as Locks, that wrap multiple calls\n\nThis gem originally arose out of a need for high-concurrency atomic operations;\nfor a fun rant on the topic, see [An Atomic Rant](http:\u002F\u002Fnateware.com\u002F2010\u002F02\u002F18\u002Fan-atomic-rant),\nor scroll down to [Atomic Counters and Locks](#atomicity) in this README.\n\nThere are two ways to use Redis::Objects, either as an include within a [model class](#option-1-model-class-include) (to\ntightly integrate with ORMs or other classes), or [standalone](#option-2-standalone-usage) by using classes such\nas `Redis::List` and `Redis::SortedSet`.\n\n# Installation and Setup\n\nAdd it to your Gemfile as:\n\n```ruby\ngem 'redis-objects'\n```\n\nRedis::Objects needs a handle created by `Redis.new` or a [ConnectionPool](https:\u002F\u002Fgithub.com\u002Fmperham\u002Fconnection_pool).\n\nIf you're using Rails, `config\u002Finitializers\u002Fredis.rb` is a good place for this.\nHowever, there are **no** dependencies on Rails. Redis::Objects can be used in any Ruby code; Sinatra,\nResque, or Standalone - no problem.\n\nThe recommended approach is to use a `ConnectionPool` since this guarantees that most timeouts in the `redis` client\ndo not pollute your existing connection.\n\n```ruby\nrequire 'connection_pool'\nRedis::Objects.redis = ConnectionPool.new(size: 5, timeout: 5) { Redis.new(:host => '127.0.0.1', :port => 6379) }\n```\n\nHowever, you need to make sure that both `:timeout` and `:size` are set appropriately\nin a multithreaded environment.\n\nAlternatively, you can set the `redis` handle directly:\n\n```ruby\nRedis::Objects.redis = Redis.new(...)\n```\n\nRedis::Objects will also default to `Redis.current` if `Redis::Objects.redis` is not set.\n\n```ruby\nRedis.current = Redis.new(:host => '127.0.0.1', :port => 6379)\n```\n\nFinally, you can even set different handles for different classes:\n\n```ruby\nclass User\n  include Redis::Objects\nend\nclass Post\n  include Redis::Objects\nend\n\n# you can also use a ConnectionPool here as well\nUser.redis = Redis.new(:host => '1.2.3.4')\nPost.redis = Redis.new(:host => '5.6.7.8')\n```\n\nAs of `0.7.0`, `redis-objects` now autoloads the appropriate `Redis::Whatever`\nclasses on demand. Previous strategies of individually requiring `redis\u002Flist`\nor `redis\u002Fset` are no longer required.\n\n# Option 1: Model Class Include\n\nIncluding Redis::Objects in a model class makes it trivial to integrate Redis types\nwith an existing ActiveRecord, DataMapper, Mongoid, or similar class. **Redis::Objects\nwill work with _any_ class that provides an `id` method that returns a unique value.**\nRedis::Objects automatically creates keys that are unique to each object, in the format:\n\n    model_name:id:field_name\n\nFor illustration purposes, consider this stub class:\n\n```ruby\nclass User\n  include Redis::Objects\n  counter :my_posts\n  def id\n    1\n  end\nend\n\nuser = User.new\nuser.id  # 1\nuser.my_posts.increment\nuser.my_posts.increment\nuser.my_posts.increment\nputs user.my_posts.value # 3\nuser.my_posts.reset\nputs user.my_posts.value # 0\nuser.my_posts.reset 5\nputs user.my_posts.value # 5\n```\n\nHere's an example that integrates several data types with an ActiveRecord model:\n\n```ruby\nclass Team \u003C ActiveRecord::Base\n  include Redis::Objects\n\n  redis_lock :trade_players, :expiration => 15  # sec\n  value :at_bat\n  counter :hits\n  counter :runs\n  counter :outs\n  counter :inning, :start => 1\n  list :on_base\n  list :coaches, :marshal => true\n  set  :outfielders\n  hash_key :pitchers_faced  # \"hash\" is taken by Ruby\n\n  # Customized keys\n  counter :player_totals, :key => 'players\u002F#{username}\u002Ftotal'\n  list :all_player_stats, :key => 'players:all_stats', :global => true\n  set :total_wins,        :key => 'players:#{id}:all_stats'\n  value :my_rank,         :key => 'players:my_rank:#{username}'\n\n  def id; @id; end\n  def username; \"user#{id}\"; end\nend\n```\n\nFamiliar Ruby array operations Just Work™:\n\n```ruby\n@team = Team.find_by_name('New York Yankees')\n@team.on_base \u003C\u003C 'player1'\n@team.on_base \u003C\u003C 'player2'\n@team.on_base \u003C\u003C 'player3'\n@team.on_base    # ['player1', 'player2', 'player3']\n@team.on_base.pop\n@team.on_base.shift\n@team.on_base.length  # 1\n@team.on_base.delete('player2')\n@team.on_base = ['player1', 'player2']  # ['player1', 'player2']\n```\n\nSets work too:\n\n```ruby\n@team.outfielders \u003C\u003C 'outfielder1'\n@team.outfielders \u003C\u003C 'outfielder2'\n@team.outfielders \u003C\u003C 'outfielder1'   # dup ignored\n@team.outfielders  # ['outfielder1', 'outfielder2']\n@team.outfielders.each do |player|\n  puts player\nend\nplayer = @team.outfielders.detect{|of| of == 'outfielder2'}\n@team.outfielders = ['outfielder1', 'outfielder3']  # ['outfielder1', 'outfielder3']\n```\n\nHashes work too:\n\n```ruby\n@team.pitchers_faced['player1'] = 'pitcher2'\n@team.pitchers_faced['player2'] = 'pitcher1'\n@team.pitchers_faced = { 'player1' => 'pitcher2', 'player2' => 'pitcher1' }\n```\n\nAnd you can do unions and intersections between objects (kinda cool):\n\n```ruby\n@team1.outfielders | @team2.outfielders   # outfielders on both teams\n@team1.outfielders & @team2.outfielders   # in baseball, should be empty :-)\n```\n\nCounters can be atomically incremented\u002Fdecremented (but not assigned):\n\n```ruby\n@team.hits.increment  # or incr\n@team.hits.decrement  # or decr\n@team.hits.incr(3)    # add 3\n@team.runs = 4        # exception\n```\n\nDefining a different method as the `id` field is easy\n\n```ruby\nclass User\n  include Redis::Objects\n  redis_id_field :uid\n  counter :my_posts\nend\n\nuser.uid                # 195137a1bdea4473\nuser.my_posts.increment # 1\n```\n\nYou can also define globals redis attributes that are accessed through the class itself.\nNo id needed\u002Fused for these.\n\n```ruby\nclass Team \u003C ActiveRecord::Base\n  include Redis::Objects\n\n  sorted_set :rank, :global => true\nend\n\nTeam.rank['Yankees']   = 12\nTeam.rank['Red Socks'] = 5\nTeam.rank['Mariners']  = 7\nTeam.rank.members(:with_scores => true) # => [[\"Red Socks\", 5], [\"Mariners\", 7], [\"Yankees\", 12]]\n```\n\nFinally, for free, you get a `redis` method that points directly to a Redis connection:\n\n```ruby\nTeam.redis.get('somekey')\n@team = Team.new\n@team.redis.get('somekey')\n@team.redis.smembers('someset')\n```\n\nYou can use the `redis` handle to directly call any [Redis API command](http:\u002F\u002Fredis.io\u002Fcommands).\n\n# Option 2: Standalone Usage\n\nThere is a Ruby class that maps to each Redis type, with methods for each\n[Redis API command](http:\u002F\u002Fredis.io\u002Fcommands).\nNote that calling `new` does not imply it's actually a \"new\" value - it just\ncreates a mapping between that Ruby object and the corresponding Redis data\nstructure, which may already exist on the `redis-server`.\n\n## Counters\n\nThe `counter_name` is the key stored in Redis.\n\n```ruby\n@counter = Redis::Counter.new('counter_name')\n@counter.increment  # or incr\n@counter.decrement  # or decr\n@counter.increment(3)\nputs @counter.value\n```\n\nThis gem provides a clean way to do atomic blocks as well:\n\n```ruby\n@counter.increment do |val|\n  raise \"Full\" if val > MAX_VAL  # rewind counter\nend\n```\n\nSee the section on [Atomic Counters and Locks](#atomicity) for cool uses of atomic counter blocks.\n\n## Locks\n\nA convenience class that wraps the pattern of [using setnx to perform locking](http:\u002F\u002Fredis.io\u002Fcommands\u002Fsetnx).\n\n```ruby\n@lock = Redis::Lock.new('serialize_stuff', :expiration => 15, :timeout => 0.1)\n@lock.lock do\n  # do work\nend\n```\n\nThis can be especially useful if you're running batch jobs spread across multiple hosts.\n\n## Values\n\nSimple values are easy as well:\n\n```ruby\n@value = Redis::Value.new('value_name')\n@value.value = 'a'\n@value.delete\n```\n\nComplex data is no problem with :marshal => true:\n\n```ruby\n@account = Account.create!(params[:account])\n@newest  = Redis::Value.new('newest_account', :marshal => true)\n@newest.value = @account.attributes\nputs @newest.value['username']\n```\n\nCompress data to save memory usage on Redis with :compress => true:\n\n```ruby\n@account = Account.create!(params[:account])\n@marshaled_value = Redis::Value.new('marshaled', :marshal => true, :compress => true)\n@marshaled_value.value = @account.attributes\n@unmarshaled_value = Redis::Value.new('unmarshaled', :compress => true)\n@unmarshaled_value = 'Really Long String'\nputs @marshaled_value.value['username']\nputs @unmarshaled_value.value\n```\n\n## Lists\n\nLists work just like Ruby arrays:\n\n```ruby\n@list = Redis::List.new('list_name')\n@list \u003C\u003C 'a'\n@list \u003C\u003C 'b'\n@list.include? 'c'   # false\n@list.values  # ['a','b']\n@list \u003C\u003C 'c'\n@list.delete('c')\n@list[0]\n@list[0,1]\n@list[0..1]\n@list.shift\n@list.pop\n@list.clear\n# etc\n```\n\nYou can bound the size of the list to only hold N elements like so:\n\n```ruby\n# Only holds 10 elements, throws out old ones when you reach :maxlength.\n@list = Redis::List.new('list_name', :maxlength => 10)\n```\n\nComplex data types are serialized with :marshal => true:\n\n```ruby\n@list = Redis::List.new('list_name', :marshal => true)\n@list \u003C\u003C {:name => \"Nate\", :city => \"San Diego\"}\n@list \u003C\u003C {:name => \"Peter\", :city => \"Oceanside\"}\n@list.each do |el|\n  puts \"#{el[:name]} lives in #{el[:city]}\"\nend\n```\n\nNote: If you run into issues, with Marshal errors, refer to the fix in [Issue #176](https:\u002F\u002Fgithub.com\u002Fnateware\u002Fredis-objects\u002Fissues\u002F176).\n\n## Hashes\n\nHashes work like a Ruby [Hash](http:\u002F\u002Fruby-doc.org\u002Fcore\u002Fclasses\u002FHash.html), with\na few Redis-specific additions. (The class name is \"HashKey\" not just \"Hash\", due to\nconflicts with the Ruby core Hash class in other gems.)\n\n```ruby\n@hash = Redis::HashKey.new('hash_name')\n@hash['a'] = 1\n@hash['b'] = 2\n@hash.each do |k,v|\n  puts \"#{k} = #{v}\"\nend\n@hash['c'] = 3\nputs @hash.all  # {\"a\"=>\"1\",\"b\"=>\"2\",\"c\"=>\"3\"}\n@hash.clear\n```\n\nRedis also adds incrementing and bulk operations:\n\n```ruby\n@hash.incr('c', 6)  # 9\n@hash.bulk_set('d' => 5, 'e' => 6)\n@hash.bulk_get('d','e')  # \"5\", \"6\"\n```\n\nRemember that numbers become strings in Redis. Unlike with other Redis data types,\n`redis-objects` can't guess at your data type in this situation, since you may\nactually mean to store \"1.5\".\n\n## Sets\n\nSets work like the Ruby [Set](http:\u002F\u002Fruby-doc.org\u002Fcore\u002Fclasses\u002FSet.html) class.\nThey are unordered, but guarantee uniqueness of members.\n\n```ruby\n@set = Redis::Set.new('set_name')\n@set \u003C\u003C 'a'\n@set \u003C\u003C 'b'\n@set \u003C\u003C 'a'  # dup ignored\n@set.member? 'c'      # false\n@set.members          # ['a','b']\n@set.members.reverse  # ['b','a']\n@set.each do |member|\n  puts member\nend\n@set.clear\n# etc\n```\n\nYou can perform Redis intersections\u002Funions\u002Fdiffs easily:\n\n```ruby\n@set1 = Redis::Set.new('set1')\n@set2 = Redis::Set.new('set2')\n@set3 = Redis::Set.new('set3')\nmembers = @set1 & @set2   # intersection\nmembers = @set1 | @set2   # union\nmembers = @set1 + @set2   # union\nmembers = @set1 ^ @set2   # difference\nmembers = @set1 - @set2   # difference\nmembers = @set1.intersection(@set2, @set3)  # multiple\nmembers = @set1.union(@set2, @set3)         # multiple\nmembers = @set1.difference(@set2, @set3)    # multiple\n```\n\nOr store them in Redis:\n\n```ruby\n@set1.interstore('intername', @set2, @set3)\nmembers = @set1.redis.get('intername')\n@set1.unionstore('unionname', @set2, @set3)\nmembers = @set1.redis.get('unionname')\n@set1.diffstore('diffname', @set2, @set3)\nmembers = @set1.redis.get('diffname')\n```\n\nAnd use complex data types too, with :marshal => true:\n\n```ruby\n@set1 = Redis::Set.new('set1', :marshal => true)\n@set2 = Redis::Set.new('set2', :marshal => true)\n@set1 \u003C\u003C {:name => \"Nate\",  :city => \"San Diego\"}\n@set1 \u003C\u003C {:name => \"Peter\", :city => \"Oceanside\"}\n@set2 \u003C\u003C {:name => \"Nate\",  :city => \"San Diego\"}\n@set2 \u003C\u003C {:name => \"Jeff\",  :city => \"Del Mar\"}\n\n@set1 & @set2  # Nate\n@set1 - @set2  # Peter\n@set1 | @set2  # all 3 people\n```\n\n## Sorted Sets\n\nDue to their unique properties, Sorted Sets work like a hybrid between\na Hash and an Array. You assign like a Hash, but retrieve like an Array:\n\n```ruby\n@sorted_set = Redis::SortedSet.new('number_of_posts')\n@sorted_set['Nate']  = 15\n@sorted_set['Peter'] = 75\n@sorted_set['Jeff']  = 24\n\n# Array access to get sorted order\n@sorted_set[0..2]           # => [\"Nate\", \"Jeff\", \"Peter\"]\n@sorted_set[0,2]            # => [\"Nate\", \"Jeff\"]\n\n@sorted_set['Peter']        # => 75\n@sorted_set['Jeff']         # => 24\n@sorted_set.score('Jeff')   # same thing (24)\n\n@sorted_set.rank('Peter')   # => 2\n@sorted_set.rank('Jeff')    # => 1\n\n@sorted_set.first           # => \"Nate\"\n@sorted_set.last            # => \"Peter\"\n@sorted_set.revrange(0,2)   # => [\"Peter\", \"Jeff\", \"Nate\"]\n\n@sorted_set['Newbie'] = 1\n@sorted_set.members         # => [\"Newbie\", \"Nate\", \"Jeff\", \"Peter\"]\n@sorted_set.members.reverse # => [\"Peter\", \"Jeff\", \"Nate\", \"Newbie\"]\n\n@sorted_set.rangebyscore(10, 100, :limit => 2)   # => [\"Nate\", \"Jeff\"]\n@sorted_set.members(:with_scores => true)        # => [[\"Newbie\", 1], [\"Nate\", 16], [\"Jeff\", 28], [\"Peter\", 76]]\n\n# atomic increment\n@sorted_set.increment('Nate')\n@sorted_set.incr('Peter')   # shorthand\n@sorted_set.incr('Jeff', 4)\n```\n\nThe other Redis Sorted Set commands are supported as well; see [Sorted Sets API](http:\u002F\u002Fredis.io\u002Fcommands#sorted_set).\n\n\u003Ca name=\"atomicity\">\u003C\u002Fa>\nAtomic Counters and Locks\n\n---\n\nYou are probably not handling atomicity correctly in your app. For a fun rant\non the topic, see [An Atomic Rant](http:\u002F\u002Fnateware.com\u002Fan-atomic-rant.html).\n\nAtomic counters are a good way to handle concurrency:\n\n```ruby\n@team = Team.find(1)\nif @team.drafted_players.increment \u003C= @team.max_players\n  # do stuff\n  @team.team_players.create!(:player_id => 221)\n  @team.active_players.increment\nelse\n  # reset counter state\n  @team.drafted_players.decrement\nend\n```\n\nAn _atomic block_ gives you a cleaner way to do the above. Exceptions or returning nil\nwill rewind the counter back to its previous state:\n\n```ruby\n@team.drafted_players.increment do |val|\n  raise Team::TeamFullError if val > @team.max_players  # rewind\n  @team.team_players.create!(:player_id => 221)\n  @team.active_players.increment\nend\n```\n\nHere's a similar approach, using an if block (failure rewinds counter):\n\n```ruby\n@team.drafted_players.increment do |val|\n  if val \u003C= @team.max_players\n    @team.team_players.create!(:player_id => 221)\n    @team.active_players.increment\n  end\nend\n```\n\nClass methods work too, using the familiar ActiveRecord counter syntax:\n\n```ruby\nTeam.increment_counter :drafted_players, team_id\nTeam.decrement_counter :drafted_players, team_id, 2\nTeam.increment_counter :total_online_players  # no ID on global counter\n```\n\nClass-level atomic blocks can also be used. This may save a DB fetch, if you have\na record ID and don't need any other attributes from the DB table:\n\n```ruby\nTeam.increment_counter(:drafted_players, team_id) do |val|\n  TeamPitcher.create!(:team_id => team_id, :pitcher_id => 181)\n  Team.increment_counter(:active_players, team_id)\nend\n```\n\n### Locks\n\nLocks work similarly. On completion or exception the lock is released:\n\n```ruby\nclass Team \u003C ActiveRecord::Base\n  redis_lock :reorder # declare a lock\nend\n\n@team.reorder_lock.lock do\n  @team.reorder_all_players\nend\n```\n\nClass-level lock (same concept)\n\n```ruby\nTeam.obtain_lock(:reorder, team_id) do\n  Team.reorder_all_players(team_id)\nend\n```\n\nLock expiration. Sometimes you want to make sure your locks are cleaned up should\nthe unthinkable happen (server failure). You can set lock expirations to handle\nthis. Expired locks are released by the next process to attempt lock. Just\nmake sure you expiration value is sufficiently large compared to your expected\nlock time.\n\n```ruby\nclass Team \u003C ActiveRecord::Base\n  redis_lock :reorder, :expiration => 15.minutes\nend\n```\n\nKeep in mind that true locks serialize your entire application at that point. As\nsuch, atomic counters are strongly preferred.\n\n### Expiration\n\nUse :expiration and :expireat options to set default expiration.\n\n```ruby\nvalue :value_with_expiration, :expiration => 1.hour\nvalue :value_with_expireat, :expireat => lambda { Time.now + 1.hour }\n```\n\n:warning: In the above example, `expiration` is evaluated at class load time.\nIn this example, it will be one hour after loading the class, not after one hour\nafter setting a value. If you want to expire one hour after setting the value,\nplease use `:expireat` with `lambda`.\n\n## Custom serialization\n\nYou can customize how values are serialized by setting `serializer: CustomSerializer`.\nThe default is `Marshal` from the standard lib, but it can be anything that responds to `dump` and\n`load`. `JSON` and `YAML` are popular options.\n\nIf you need to pass extra arguments to `dump` or `load`, you can set\n`marshal_dump_args: { foo: 'bar' }` and `marshal_load_args: { foo: 'bar' }` respectively.\n\n```ruby\nclass CustomSerializer\n  def self.dump(value)\n    # custom code for serializing\n  end\n\n  def self.load(value)\n    # custom code for deserializing\n  end\nend\n\n@account = Account.create!(params[:account])\n@newest  = Redis::Value.new('custom_serializer', marshal: true, serializer: CustomSerializer)\n@newest.value = @account.attributes\n```\n\n---\n\n## Under the Hood\n\nRedis keys are prefixed to namespace keys with the same names.\nBy default the prefix is generated from the embedded class's name.\nBut you can also set a custom prefix using `redis_prefix=`.\n\nIf needed the redis key for a specific attribute of a class can be obtained by:\n`MyClass.redis_field_key(attr_name, primary_id)`\nor for globals\n`MyClass.redis_field_key(attr_name)`\n\n## Author\n\nCopyright (c) 2009-2026 [Nate Wiger](http:\u002F\u002Fnateware.com). All Rights Reserved.\nReleased under the [Artistic License](http:\u002F\u002Fwww.opensource.org\u002Flicenses\u002Fartistic-license-2.0.php).\n","redis-objects 是一个将 Redis 数据类型直接映射到 Ruby 对象的库。其核心功能包括通过简洁的 API 访问 Redis 的各种数据结构，如计数器、列表和集合，并支持原子操作。该库解决了在 Ruby 应用程序中高效利用 Redis 作为数据存储的问题，特别适合需要高性能缓存或实时数据处理的应用场景。此外，它还提供了一些高级特性，比如锁机制和自定义键命名方案，以满足更复杂的应用需求。",2,"2026-06-11 03:15:47","top_language"]