{"id":13879168,"url":"https://github.com/codenoble/cache-crispies","last_synced_at":"2025-10-10T14:14:03.323Z","repository":{"id":35013692,"uuid":"196282439","full_name":"codenoble/cache-crispies","owner":"codenoble","description":"Speedy Rails JSON serialization with built-in caching","archived":false,"fork":false,"pushed_at":"2025-03-23T03:41:55.000Z","size":162,"stargazers_count":162,"open_issues_count":12,"forks_count":15,"subscribers_count":5,"default_branch":"main","last_synced_at":"2025-10-09T05:40:03.311Z","etag":null,"topics":["cache","caching","json","rails","ruby"],"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/codenoble.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"MIT-LICENSE","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,"zenodo":null}},"created_at":"2019-07-10T22:24:09.000Z","updated_at":"2025-08-20T13:52:46.000Z","dependencies_parsed_at":"2023-02-16T03:45:58.991Z","dependency_job_id":"f26d8f94-258c-48b5-aa7c-8cf4852408e2","html_url":"https://github.com/codenoble/cache-crispies","commit_stats":{"total_commits":120,"total_committers":11,"mean_commits":"10.909090909090908","dds":0.5916666666666667,"last_synced_commit":"9a3e3060da452a37d896565494c1cdf9c7e23c55"},"previous_names":[],"tags_count":17,"template":false,"template_full_name":null,"purl":"pkg:github/codenoble/cache-crispies","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codenoble%2Fcache-crispies","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codenoble%2Fcache-crispies/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codenoble%2Fcache-crispies/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codenoble%2Fcache-crispies/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/codenoble","download_url":"https://codeload.github.com/codenoble/cache-crispies/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codenoble%2Fcache-crispies/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":279004178,"owners_count":26083688,"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-10-10T02:00:06.843Z","response_time":62,"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":["cache","caching","json","rails","ruby"],"created_at":"2024-08-06T08:02:11.998Z","updated_at":"2025-10-10T14:14:03.306Z","avatar_url":"https://github.com/codenoble.png","language":"Ruby","readme":"[![Ruby](https://github.com/codenoble/cache-crispies/actions/workflows/rspec.yml/badge.svg)](https://github.com/codenoble/cache-crispies/actions/workflows/rspec.yml) [![Codacy Badge](https://app.codacy.com/project/badge/Coverage/656eaf5139ce4a16a465676d8acc7eac)](https://app.codacy.com/gh/codenoble/cache-crispies/dashboard?utm_source=gh\u0026utm_medium=referral\u0026utm_content=\u0026utm_campaign=Badge_coverage) [![Codacy Badge](https://app.codacy.com/project/badge/Grade/656eaf5139ce4a16a465676d8acc7eac)](https://app.codacy.com/gh/codenoble/cache-crispies/dashboard?utm_source=gh\u0026utm_medium=referral\u0026utm_content=\u0026utm_campaign=Badge_grade)\n==============\n\nSpeedy Rails JSON serialization with built-in caching.\n\nWhy?\n----\n\nThere are a lot of Rails serializers out there, but there seem to be very few these days that are well maintained and performant. The ones that are, tend to lock you into a specific standard for how to format your JSON responses. And the idea of introducing breaking API changes across the board to a mature Rails app is daunting, to say the least.\n\nIn addition, incorporating a caching layer (for performance reasons) into your serializers can be difficult unless you do it at a Rails view layer. And the serialization gems that work at the view layer tend to be slow in comparison to others. So it tends to be a one step forward one step back sort of solution.\n\nIn light of all that, this gem was built with these goals in mind:\n1.  Be fast\n2.  Support caching in as simple a way as we can\n3.  Support rollout without causing breaking API changes\n4.  Avoid the bloat that can lead to slowness and maintenance difficulties\n\nRequirements\n------------\n-   Ruby 2.4–2.6 _(others will likely work but are untested)_\n-   Rails 5 or 6 _(others may work but are untested)_\n\nFeatures\n--------\n-   **Fast** even without caching\n-   **Flexible** lets you serialize data any way you want it\n-   **Built-in Caching** _(documentation coming soon)_\n-   **ETags** for easy HTTP caching\n-   **Simple, Readable DSL**\n\nConfiguration\n-------------\n### ETags\n```ruby\nCacheCrispies.configure do |conf|\n  conf.etags = true\nend\n```\n_`etags` is set to `false` by default._\n\n### Custom Cache Store\n```ruby\nCacheCrispies.configure do |conf|\n  conf.cache_store = ActiveSupport::Cache::DalliStore.new('localhost')\nend\n```\n`cache_store` must be set to something that quacks like a [ActiveSupport::Cache::Store](https://api.rubyonrails.org/classes/ActiveSupport/Cache/Store.html).\n\n_`cache_store` is set to `Rails.cache` by default, or `ActiveSupport::Cache::NullStore.new` if `Rails.cache` is `nil`._\n\n### Custom model cache key\n```ruby\nCacheCrispies.configure do |conf|\n  conf.cache_key_method = :custom_cache_key_method_name\nend\n```\n`cache_key_method` must be set to the name of the method the model responds to and returns a string value.\n\n_`cache_key_method` is set to `:cache_key` by default._\n\nUsage\n-----\n### A simple serializer\n```ruby\nclass CerealSerializer \u003c CacheCrispies::Base\n  serialize :name, :brand\nend\n```\n\n### A not-so-simple serializer\n```ruby\n  class CerealSerializer \u003c CacheCrispies::Base\n    key :food\n    collection_key :food\n\n    do_caching true\n\n    cache_key_addons { |options| options[:be_trendy] }\n    dependency_key 'V3'\n\n    serialize :uid, from: :id, to: String\n    serialize :name, :company\n    serialize :copyright, through: :legal_info\n    serialize :spiel do |cereal, _options|\n      'Made with whole grains!' if cereal.ingredients[:whole_grains] \u003e 0.000001\n    end\n    merge :itself, with: MarketingBsSerializer\n\n    nest_in :about do\n      nest_in :nutritional_information do\n        serialize :calories\n        serialize :ingredients, with: IngredientSerializer, optional: true\n      end\n    end\n\n    show_if -\u003e(_model, options) { options[:be_trendy] } do\n      nest_in :health do\n        serialize :organic\n\n        show_if -\u003e(model) { model.organic } do\n          serialize :certification\n        end\n      end\n    end\n\n    def certification\n      'Totally Not A Scam Certifiers Inc'\n    end\n  end\n```\n\nPut serializer files in `app/serializers/`. For instance this file should be at `app/serializers/cereal_serializer.rb`.\n\n### In your Rails controller\n```ruby\nclass CerealsController\n  include CacheCrispies::Controller\n\n  def index\n    cereals = Cereal.all\n    cache_render CerealSerializer, cereals, custom_option: true\n  end\nend\n```\n\n### Anywhere else\n```ruby\nCerealSerializer.new(Cereal.first, be_trendy: true, include: :ingredients).as_json\n```\n\n### Output\n```json\n{\n  \"uid\": \"42\",\n  \"name\": \"Eyeholes\",\n  \"company\": \"Needful Things\",\n  \"copyright\": \"© Need Things 2019\",\n  \"spiel\": \"Made with whole grains!\",\n  \"tagline\": \"Part of a balanced breakfast\",\n  \"small_print\": \"This doesn't mean jack-squat\",\n  \"about\": {\n    \"nutritional_information\": {\n      \"calories\": 1000,\n      \"ingredients\": [\n        {\n          \"name\": \"Sugar\"\n        },\n        {\n          \"name\": \"Other Kind of Sugar\"\n        }\n      ]\n    }\n  },\n  \"health\": {\n    \"organic\": false\n  }\n}\n```\n\nA Note About Caching\n--------------------\nTurning on caching is as simple as adding `do_caching true` to your serialzer. But if you're not familiar with how Rails caching, or caching in general works you could wind up with some real messy caching bugs.\n\nAt the very least, you should know that Cache Crispies bases most of it's caching on the `cache_key` method provided by Rails Active Record models. Knowing how `cache_key` works in Rails, along with `touch`, will get you a long way. I'd recommend taking a look at the [Caching with Rails](https://guides.rubyonrails.org/caching_with_rails.html) guide if you're looking for a place to start.\n\nFor those looking for more specifics, here is the code that generates a cache key for a serializer instance:\n```ruby\n[\nCACHE_KEY_PREFIX, # \"cache-crispies\"\nserializer.cache_key_base, # an MD5 hash of the contest of the serializer file and all nested serializer files\nserializer.dependency_key, # an optional static key\naddons_key, # an optional runtime-generated key\ncacheable.cache_key # typically ActiveRecord::Base#cache_key\n].flatten.compact.join(CACHE_KEY_SEPARATOR) # + is used as the separator\n```\n\n### Key Points to Remember\n-   Caching is completely optional and disabled in serializers by default\n-   If an object you're serializing doesn't have a `cache_key` method, it won't be cached\n-   If you want to cache a model, it should have an `updated_at` column\n-   Editing an `app/serializers/____serializer.rb` file will bust all caches generated by that serializer\n-   Editing an `app/serializers/____serializer.rb` file will bust all caches generated by other serialiers that nest that serializer\n-   Changing the `dependency_key` will bust all caches from that serializer\n-   Not setting the appropriate value in `cache_key_addons` when the same model + serializer pair could produce different output, depending on options or other factors, will result in stale data\n-   Data will be cached in the `Rails.cache` store by default\n-   If the serializer is implemented in a Rails Engine instead of the base Rails application, set the engine class in the serializer: `engine MyEngine` (inherited in subclasses)\n\nHow To...\n---------\n### Use a different JSON key\n```ruby\nserialize :is_organic, from: :organic?\n```\n\n### Use an attribute from an associated object\n```ruby\nserialize :copyright, through: :legal_info\n```\n_If the `legal_info` method returns `nil`, `copyright` will also be `nil`._\n\n### Nest another serializer\n```ruby\nserialize :ingredients, with: IngredientSerializer\n```\n\n### Merge attributes from another serializer\n```ruby\nmerge :legal_info, with: LegalInfoSerializer\n```\n\n### Force another serializer to be rendered as a single or collection\n```ruby\nmerge :prices, with: PricesSerializer, collection: false\n```\n\n### Coerce to another data type\n```ruby\nserialize :id, to: String\n```\nSupported data type arguments are\n-   `String`\n-   `Integer`\n-   `Float`\n-   `BigDecimal`\n-   `Array`\n-   `Hash`\n-   `:bool`, `:boolean`, `TrueClass`, or `FalseClass`\n\n### Nest attributes\n```ruby\nnest_in :health_info do\n  serialize :non_gmo\nend\n```\n_You can nest `nest_in` blocks as deeply as you want._\n\n### Conditionally render attributes\n```ruby\nshow_if (model, options) =\u003e { model.low_carb? || options[:trendy] } do\n  serialize :keto_certified\nend\n```\n_You can nest `show_if` blocks as deeply as you want._\n\n### Render custom values\n```ruby\nserialize :fine_print do |model, options|\n  model.fine_print || options[:fine_print] || '*Contents may contain lots and lots of sugar'\nend\n```\nor\n```ruby\nserialize :fine_print\n\ndef fine_print\n  model.fine_print || options[:fine_print] || '*Contents may contain lots and lots of sugar'\nend\n```\n\n### Include other data\n```ruby\nclass CerealSerializer \u003c CacheCrispies::Base\n  serialize :page\n\n  def page\n    options[:page]\n  end\nend\n\nCerealSerializer.new(cereal, page: 42).as_json\n# or\ncache_render CerealSerializer, cereal, page: 42\n```\n\n### Include metadata\n```ruby\n  cache_render CerealSerializer, meta: { page: 42 }\n```\n\nThis would render\n```json\n{\n  \"meta\": { \"page\": 42 },\n  \"cereal\": {\n    ...\n  }\n}\n```\n_Note that metadata is not cached._\n\n### Change the default metadata key\nThe default metadata key is `meta`, but it can be changed with the `meta_key` option.\n\n```ruby\n  cache_render CerealSerializer, meta: { page: 42 }, meta_key: :pagination\n```\n\nThis would render\n```json\n{\n  \"pagination\": { \"page\": 42 },\n  \"cereal\": {\n    ...\n  }\n}\n```\n\n### Set custom JSON keys\n```ruby\nclass CerealSerializer \u003c CacheCrispies::Base\n  key :breakfast_cereal\n  collection_key :breakfast_cereals\nend\n```\n_Note that `collection_key` is the plural of `key` by default._\n\n### Force rendering as a collection or not\nBy default Cache Crispies will look at whether or not the object you're serializing responds to `#each` in order to determine whether to render it as a collection, where every item in the `Enumerable` object is individually passed to the serializer and returned as an `Array`. Or as a non-collection where the single object is serialized and returned.\n\nBut you can override this default behavior by passing `collection: true` or `collection: false` to the `cache_render` method.\n\nThis can be useful for things like wrappers around collections that contain metadata about the collection.\n\n```ruby\nclass CerealListSerializer \u003c CacheCrispies::Base\n  nest_in :meta do\n    serialize :length\n  end\n\n  serialize :cereals, from: :itself, with: CerealSerializer\nend\n\ncache_render CerealSerializer, cereals, collection: false\n```\n\n### Render a serializer to a `Hash`\n```ruby\nCerealSerializer.new(Cereal.first, trendy: true).as_json\n```\n\n### Enable Caching\n```ruby\ndo_caching true\n```\n\n### Customize the Cache Key\n```ruby\n  cache_key_addons do |options|\n    options[:current_user].id\n  end\n```\n\nBy default the model's `cache_key` is the primary thing determining how something will be cached. But sometimes, you need to take other things into consideration to prevent returning stale cache data. This is espcially common when you pass in options that change what's rendered.\n\nHere's an example:\n```ruby\nclass UserSerializer \u003c CacheCrispies::Base\n  serialize :display_name\n\n  def display_name\n    if options[:current_user] == model\n      model.full_name\n    else\n      model.initials\n    end\n  end\nend\n```\n\nIn this scenario, you should include `options[:current_user].id` in the `cache_key_addons`. Otherwise, the user's full name could get cached, and users, who shouldn't see it, would.\n\nIt is also possible to configure the method CacheCrispies calls on the model via the `config.cacheable_cache_key`\nconfiguration option.\n\n### Bust the Cache Key\n```ruby\ndependency_key 'V2'\n```\n\nCache Crispies does it's best to be aware of changes to your data and your serializers. Even tracking nested serializers. But, realistically, it can't track everything.\n\nFor instance, let's say you have a couple Rails models that have `email` fields. These fields are stored in the database as mixed case strings. But you want them lowercased in your JSON. So you decide to do something like this.\n\n```ruby\nmodule HasEmail\n  def email\n    model.email.downcase\n  end\nend\n\nclass UserSerializer \u003c CacheCrispies::Base\n  include HasEmail\n\n  do_caching true\n\n  serialize :email\nend\n```\n\nAs your app is used, keys are generated and stored with downcased emails. But then you realize that you have trailing whitespace in your emails. So you change your mixin to do `model.email.downcase.strip`. Now you've changed your data, without changing your database, or your serializer. So Cache Crispies doesn't know your data has changed and continues to render the emails with trailing whitespace.\n\nThe best solution for this problem is to do something like this:\n```ruby\nmodule HasEmail\n  CACHE_KEY = 'HasEmail-V2'\n\n  def email\n    model.email.downcase\n  end\nend\n\nclass UserSerializer \u003c CacheCrispies::Base\n  include HasEmail\n\n  do_caching true\n  dependency_key HasEmail::CACHE_KEY\n\n  serialize :email\nend\n```\n\nNow anytime you change `HasEmail` in a way that should bust the cache, just change the `CACHE_KEY` and you're good.\n\nDetailed Documentation\n----------------------\nSee [rubydoc.info/gems/cache_crispies](https://www.rubydoc.info/gems/cache_crispies/)\n\nBenchmarks and Example Application\n----------------------------------\nSee [github.com/codenoble/cache-crispies-performance-comparison](https://github.com/codenoble/cache-crispies-performance-comparison)\n\nTips\n----\nTo delete all cache entries in Redis:\n`redis-cli --scan --pattern \"*cache-crispies*\" | xargs redis-cli unlink`\n\nRunning Tests Locally\n---------------------\n\nWe use [Appraisal](https://github.com/thoughtbot/appraisal) to run tests against multiple Rails versions.\n\n```shell\nbundle exec appraisal install\nbundle exec appraisal rspec\n```\n\nContributing\n------------\n\nFeel free to contribute by opening a Pull Request. But before you do, please be sure to follow the steps below.\n\n-   Run `bundle exec appraisal install` to update all of the appropriate gemfiles.\n-   Run `bundle exec appraisal rspec` to ensure all tests are passing.\n-   Check the `rspec` output around test coverage. Try to maintain `LOC (100.0%) covered`, if at all possible.\n-   After pushing up your pull request, check the status from [CircleCI](https://circleci.com) and [Codacy](https://app.codacy.com/gh/codenoble/cache-crispies/dashboard) to ensure they pass.\n\nLicense\n-------\nMIT\n","funding_links":[],"categories":["Ruby","API Builder and Discovery"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcodenoble%2Fcache-crispies","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcodenoble%2Fcache-crispies","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcodenoble%2Fcache-crispies/lists"}