{"id":13878239,"url":"https://github.com/hopsoft/universalid","last_synced_at":"2025-04-09T11:09:08.995Z","repository":{"id":151590611,"uuid":"621689068","full_name":"hopsoft/universalid","owner":"hopsoft","description":"Fast, recursive, optimized, URL-Safe serialization for any Ruby object","archived":false,"fork":false,"pushed_at":"2024-04-08T20:50:05.000Z","size":160,"stargazers_count":380,"open_issues_count":8,"forks_count":9,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-04-02T10:11:23.142Z","etag":null,"topics":["ruby","serialization"],"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/hopsoft.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","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},"funding":{"github":"hopsoft"}},"created_at":"2023-03-31T07:17:00.000Z","updated_at":"2025-03-11T03:11:19.000Z","dependencies_parsed_at":null,"dependency_job_id":"ff17a67e-f029-4a79-af6c-3ad4bd6512cc","html_url":"https://github.com/hopsoft/universalid","commit_stats":null,"previous_names":[],"tags_count":9,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hopsoft%2Funiversalid","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hopsoft%2Funiversalid/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hopsoft%2Funiversalid/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hopsoft%2Funiversalid/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/hopsoft","download_url":"https://codeload.github.com/hopsoft/universalid/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248027407,"owners_count":21035594,"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","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":["ruby","serialization"],"created_at":"2024-08-06T08:01:43.621Z","updated_at":"2025-04-09T11:09:08.979Z","avatar_url":"https://github.com/hopsoft.png","language":"Ruby","readme":"# Universal ID\n\n\u003cp\u003e\n  \u003ca href=\"http://blog.codinghorror.com/the-best-code-is-no-code-at-all/\"\u003e\n    \u003cimg alt=\"Lines of Code\" src=\"https://img.shields.io/badge/loc-811-47d299.svg\" /\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://codeclimate.com/github/hopsoft/universalid/maintainability\"\u003e\n    \u003cimg src=\"https://api.codeclimate.com/v1/badges/567624cbe733fafc2330/maintainability\" /\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://rubygems.org/gems/universalid\"\u003e\n    \u003cimg alt=\"GEM Version\" src=\"https://img.shields.io/gem/v/universalid?color=168AFE\u0026include_prereleases\u0026logo=ruby\u0026logoColor=FE1616\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://rubygems.org/gems/universalid\"\u003e\n    \u003cimg alt=\"GEM Downloads\" src=\"https://img.shields.io/gem/dt/universalid?color=168AFE\u0026logo=ruby\u0026logoColor=FE1616\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://github.com/testdouble/standard\"\u003e\n    \u003cimg alt=\"Ruby Style\" src=\"https://img.shields.io/badge/style-standard-168AFE?logo=ruby\u0026logoColor=FE1616\" /\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://gitpod.io/#https://github.com/hopsoft/universalid\"\u003e\n    \u003cimg alt=\"Gitpod - Ready to Code\" src=\"https://img.shields.io/badge/Gitpod-Ready--to--Code-green?style=flat\u0026logo=gitpod\u0026logoColor=white\" /\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://github.com/hopsoft/universalid/actions/workflows/tests.yml\"\u003e\n    \u003cimg alt=\"Tests\" src=\"https://github.com/hopsoft/universalid/actions/workflows/tests.yml/badge.svg\" /\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://github.com/sponsors/hopsoft\"\u003e\n    \u003cimg alt=\"Sponsors\" src=\"https://img.shields.io/github/sponsors/hopsoft?color=eb4aaa\u0026logo=GitHub%20Sponsors\" /\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://ruby.social/@hopsoft\"\u003e\n    \u003cimg alt=\"Ruby.Social Follow\" src=\"https://img.shields.io/mastodon/follow/000008274?domain=https%3A%2F%2Fruby.social\u0026label=%40hopsoft\u0026style=social\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://twitter.com/hopsoft\"\u003e\n    \u003cimg alt=\"Twitter Follow\" src=\"https://img.shields.io/twitter/url?label=%40hopsoft\u0026style=social\u0026url=https%3A%2F%2Ftwitter.com%2Fhopsoft\"\u003e\n  \u003c/a\u003e\n\u003c/p\u003e\n\n## Fast, recursive, optimized, URL-Safe serialization for any Ruby object\n\nUniversal ID leverages both [MessagePack](https://msgpack.org/) and [Brotli](https://github.com/google/brotli) _(a combo built for speed and best-in-class data compression)_.\nWhen combined, these libraries are up to 30% faster and within 2-5% compression rates compared to Protobuf. \u003ca title=\"Source\" href=\"https://g.co/bard/share/e5bdb17aee91\"\u003e↗\u003c/a\u003e\n\nUniversal ID introduces a paradigm shift that enables straightforward simple [**solutions** ↗](docs/use_cases.md) for a variety of complex problem domains.\n\n\u003e [!TIP]\n\u003e All the code examples below can be tested on your local machine. Just clone the repo _(↑or use Gitpod above↑)_ and run `bin/console` to begin exploring.\n\u003e Don't forget to execute `bundle` first to ensure all dependencies are up to date. **Happy coding!**\n\n\u003c!-- Tocer[start]: Auto-generated, don't remove. --\u003e\n\n## Table of Contents\n\n  - [URI::UID](#uriuid)\n  - [Supported Data Types](#supported-data-types)\n    - [Primitive Types](#primitive-types)\n    - [Composite Types](#composite-types)\n    - [Extension Types](#extension-types)\n    - [Custom Types](#custom-types)\n  - [Options](#options)\n  - [Advanced Usage](#advanced-usage)\n    - [Fingerprinting](#fingerprinting)\n    - [Copy ActiveRecord Models](#copy-activerecord-models)\n    - [ActiveRecord::Relations](#activerecordrelations)\n    - [SignedGlobalID](#signedglobalid)\n  - [Sponsors](#sponsors)\n  - [License](#license)\n\n\u003c!-- Tocer[finish]: Auto-generated, don't remove. --\u003e\n\n## URI::UID\n\nUniversal ID introduces a new URI defintion that can recursively serialize any Ruby object into an URL-safe string\nwhich can be safely transported via HTTP.\n\n\u003e [!NOTE]\n\u003e The payload is optimized to be as small as possible... _especially notable with large objects._\n\nThe best part: **The API is simple.**\n\n```ruby\ndata = :ANY_OBJECT_YOU_CAN_IMAGINE\n\nuid = URI::UID.build(data)\n#\u003cURI::UID payload=Cw6AxxoAQU5ZX09CSkVDVF9ZT1VfQ0FOX0lNQ..., fingerprint=CwWAkccHf6ZTeW1ib2wD\u003e\n\nuid.payload\n\"Cw6AxxoAQU5ZX09CSkVDVF9ZT1VfQ0FOX0lNQUdJTkUD\"\n\nuid.fingerprint\n\"CwWAkccHf6ZTeW1ib2wD\"\n\nuri = uid.to_s\n\"uid://universalid/Cw6AxxoAQU5ZX09CSkVDVF9ZT1VfQ0FOX0lNQUdJTkUD#CwWAkccHf6ZTeW1ib2wD\"\n\nparsed = URI::UID.parse(uri)\n#\u003cURI::UID payload=Cw6AxxoAQU5ZX09CSkVDVF9ZT1VfQ0FOX0lNQ..., fingerprint=CwWAkccHf6ZTeW1ib2wD\u003e\n\nparsed.decode\n:ANY_OBJECT_YOU_CAN_IMAGINE\n\n# it's also possible to parse the payload by itself\n\nparsed = URI::UID.from_payload(uid.payload)\n#\u003cURI::UID payload=Cw6AxxoAQU5ZX09CSkVDVF9ZT1VfQ0FOX0lNQ..., fingerprint=CwWAkccHf6ZTeW1ib2wD\u003e\n\nparsed.decode\n:ANY_OBJECT_YOU_CAN_IMAGINE\n```\n\n## Supported Data Types\n\n### Primitive Types\n\nUniversal ID supports most native Ruby primitives:\n\n- `NilClass`\n- `BigDecimal`\n- `Complex`\n- `Date`\n- `DateTime`\n- `FalseClass`\n- `Float`\n- `Integer`\n- `Range`\n- `Rational`\n- `Regexp`\n- `String`\n- `Symbol`\n- `Time`\n- `TrueClass`\n\nYou can use Universal ID to serialize individual primitives, but this actually serves as the foundation for more advanced use-cases.\n\n```ruby\nuri = URI::UID.build(:demo).to_s\n#=\u003e \"uid://universalid/iwKA1gBkZW1vAw#CwWAkccHf6ZTeW1ib2wD\"\n\nuid = URI::UID.parse(uri)\n#=\u003e #\u003cURI::UID payload=iwKA1gBkZW1vAw, fingerprint=CwWAkccHf6ZTeW1ib2wD\u003e\n\nuid.decode\n#=\u003e :demo\n```\n\n### Composite Types\n\nComposite _(or complex, compound, etc.)_ datatype support is where things start to get interesting.\nUniversal ID supports the following native Ruby composite datatypes:\n\n- `Array`\n- `Hash`\n- `OpenStruct`\n- `Set`\n- `Struct`\n\n```ruby\narray = [1, 2, 3, [:a, :b, :c, [true]]]\n\nuri = URI::UID.build(array).to_s\n#=\u003e \"uid://universalid/iweAlAECA5TUAGHUAGLUAGORwwM#iwSAkccGf6VBcnJheQM\"\n\nuid = URI::UID.parse(uri)\n#=\u003e #\u003cURI::UID payload=iweAlAECA5TUAGHUAGLUAGORwwM, fingerprint=iwSAkccGf6VBcnJheQM\u003e\n\nuid.decode\n#=\u003e [1, 2, 3, [:a, :b, :c, [true]]]\n\nuid.decode == array\n#=\u003e true\n```\n\n```ruby\nhash = {a: 1, b: 2, c: 3, array: [1, 2, 3, [:a, :b, :c, [true]]]}\n\nuri = URI::UID.build(hash).to_s\n#=\u003e \"uid://universalid/CxKAhNQAYQHUAGIC1ABjA8cFAGFycmF5lAECA5TUAGHUAGLUAGORwwM#CwS...\"\n\nuid = URI::UID.parse(uri)\n#=\u003e #\u003cURI::UID payload=CxKAhNQAYQHUAGIC1ABjA8cFAGFycmF5lAECA..., fingerprint=CwSAkccFf6RIYXNoAw\u003e\n\nuid.decode\n#=\u003e {:a=\u003e1, :b=\u003e2, :c=\u003e3, :array=\u003e[1, 2, 3, [:a, :b, :c, [true]]]}\n\nuid.decode == hash\n#=\u003e true\n```\n\n```ruby\nBook = Struct.new(:title, :author, :isbn, :published_year)\nbook = Book.new(\"The Great Gatsby\", \"F. Scott Fitzgerald\", \"9780743273565\", 1925)\n\nuri = URI::UID.build(book).to_s\n#=\u003e \"uid://universalid/G2YAoGTomv9tT1ilLRgVC9vIpmuBo-k84FZ0G8-siFMBNsbW0dpBE0Tnm96...\"\n\nuid = URI::UID.parse(uri)\n#=\u003e #\u003cURI::UID payload=G2YAoGTomv9tT1ilLRgVC9vIpmuBo-k84FZ0G..., fingerprint=CwSAkccFf6RCb29rAw\u003e\n\nuid.decode\n#=\u003e #\u003cstruct Book title=\"The Great Gatsby\", author=\"F. Scott Fitzgerald\", isbn=\"9780743273565\", published_year=1925\u003e\n\nuid.decode == book\n#=\u003e true\n```\n\n### Extension Types\n\nThe following extension datatypes ship with Universal ID:\n\n- `ActiveRecord::Base`\n- `ActiveRecord::Relation`\n- `ActiveSupport::Cache::Entry`\n- `ActiveSupport::Cache::Store`\n- `ActiveSupport::TimeWithZone`\n- `GlobalID`\n- `SignedGlobalID`\n\n\u003e [!NOTE]\n\u003e Extensions are autoloaded whenever the related datatype is detected.\n\n\u003e [!IMPORTANT]\n\u003e **Why Universal ID with ActiveRecord?**\n\u003e ActiveRecord already has GlobalID, a robust library for serializing individual models.\n\u003e **Universal ID covers a much wider range of use cases**.\n\nHere are a few reasons you may want to consider Universal ID with ActiveRecord.\n\n- **New Records**:\n  Universal ID can serialize models that haven't been saved to the database yet.\n\n- **Changesets**:\n  Universal ID can serialize ActiveRecord models with unsaved changes, ensuring that even transient states are captured.\n\n- **Associations**:\n  Universal ID goes beyond single models. It can include associated records, even those with unsaved changes, creating a comprehensive snapshot of complex record states.\n\n- **Copying/Cloning**:\n  Universal ID supports making copies of records _(including associations)_, making it ideal for duplicating complex datasets.\n\n- **More Control**:\n  Universal ID gives you control over the serialization process. You can choose which columns to include/exclude, allowing for tailored, optimized payloads to fit your needs.\n\n- **Queries/Relations**:\n  Universal ID also supports ActiveRecord::Relations, enabling the serialization of complex database queries and scopes.\n\nIn summary, while GlobalID excels in its specific use case, Universal ID offers more power for use-cases that involve unsaved records, complex associations, data cloning, and database queries.\n\n```ruby\n# setup some records\ncampaign = Campaign.create(name: \"My Campaign\")\nemail = campaign.emails.create(subject: \"First Email\")\nattachment = email.attachments.create(file_name: \"data.pdf\")\n\n# ensure associations are loaded so they can be included in an UID\ncampaign.emails.load\ncampaign.emails.each { |e| e.attachments.load }\n\n# make some unsaved changes\nemail.subject = \"1st Email\"\n\n# add an unsaved record\ncampaign.emails.build(subject: \"2nd Email\")\n\n# introspection\ncampaign.emails.size #=\u003e 2\ncampaign.emails.loaded? #=\u003e true\ncampaign.emails.last.new_record? #=\u003e true\n\noptions = {\n  include_changes: true,\n  include_descendants: true,\n  descendant_depth: 2\n}\n\nuri = URI::UID.build(campaign, options).to_s\n#=\u003e \"uid://universalid/GxYBYGT6_Xn_OrelIDRWhQQgvbS5gQxV7EJKe3paIiEFmEEc1gLKw8Pl2-k...\"\n\nuid = URI::UID.parse(uri)\n#=\u003e #\u003cURI::UID payload=GxYBYGT6_Xn_OrelIDRWhQQgvbS5gQxV7EJKe..., fingerprint=CwuAkscJf6hDYW1wYWlnbtf_ReuZnGWeG5MD\u003e\n\ndecoded = uid.decode\n#=\u003e \"#\u003cCampaign id: 13, name: \\\"My Campaign\\\" ...\u003e\n\ndecoded == campaign\n#=\u003e true\n\n# introspection\ndecoded.emails.size #=\u003e 2\ndecoded.emails.loaded? #=\u003e true\ndecoded.emails.first.changed? #=\u003e true\ndecoded.emails.first.changes #=\u003e {\"subject\"=\u003e[\"First Email\", \"1st Email\"]}\ndecoded.emails.last.new_record? #=\u003e true\ndecoded.save #=\u003e true\ndecoded.emails.last.persisted? #=\u003e true\n```\n\n### Custom Types\n\nUniversal ID is extensible, enabling you to register your own datatypes with custom serialization rules.\nSimply convert the required data to a Ruby primitive or composite value.\n\n```ruby\n# create a custom type\nclass UserSettings\n  attr_accessor :user_id, :preferences\n\n  def initialize(user_id, preferences = {})\n    @user_id = user_id\n    @preferences = preferences\n  end\nend\n\n# register the custom type with Universal ID\nUniversalID::MessagePackFactory.register(\n  type: UserSettings,\n  packer: -\u003e(user_preferences, packer) do\n    packer.write user_preferences.user_id\n    packer.write user_preferences.preferences\n  end,\n  unpacker: -\u003e(unpacker) do\n    user_id = unpacker.read\n    preferences = unpacker.read\n    UserSettings.new user_id, preferences\n  end\n)\n\n# create an instance of the custom type\nsettings = UserSettings.new(1,\n  theme: \"dark\",\n  notifications: \"email\",\n  language: \"en\",\n  layout: \"grid\",\n  privacy: \"private\"\n)\n\n# serialize the custom type\nuri = URI::UID.build(settings).to_s\n#=\u003e \"uid://universalid/G1QAQAT-c_cO7qJcAk-TtsAiadci_IA5xoH7NV3bYttEww7xuUkzasu2HEO...\"\n\n# deserialize the custom type\nuid = URI::UID.parse(uri)\n#=\u003e #\u003cURI::UID payload=G1QAQAT-c_cO7qJcAk-TtsAiadci_IA5xoH7N..., fingerprint=CwiAkccNf6xVc2VyU2V0dGluZ3MD\u003e\n\nuid.decode\n=\u003e #\u003cUserSettings:0x000000011d0deb20 @preferences={:theme=\u003e\"dark\", :notifications=\u003e\"email\", :language=\u003e\"en\", :layout=\u003e\"grid\", :privacy=\u003e\"private\"}, @user_id=1\u003e\n```\n\n## Options\n\nUniversal ID supports a small, but powerful, set of options used to \"prepack\" the object before it's packed with MessagePack.\nThese options instruct Universal ID on how to prepare the object for serialization.\n\n```yml\nprepack:\n  # ..........................................................................................................\n  # A list of attributes to exclude (for objects like Hash, OpenStruct, Struct, etc.)\n  # Takes prescedence over the`include` list\n  exclude: []\n\n  # ..........................................................................................................\n  # A list of attributes to include (for objects like Hash, OpenStruct, Struct, etc.)\n  include: []\n\n  # ..........................................................................................................\n  # Whether or not to include blank values when packing (nil, {}, [], \"\", etc.)\n  include_blank: true\n\n  # ==========================================================================================================\n  # Database records\n  database:\n    # ......................................................................................................\n    # Whether or not to include primary/foreign keys\n    # Setting this to `false` can be used to make a copy of an existing record\n    include_keys: true\n\n    # ......................................................................................................\n    # Whether or not to include date/time timestamps (created_at, updated_at, etc.)\n    # Setting this to `false` can be used to make a copy of an existing record\n    include_timestamps: true\n\n    # ......................................................................................................\n    # Whether or not to include unsaved changes\n    # Assign to `true` when packing new records\n    include_changes: false\n\n    # ......................................................................................................\n    # Whether or not to include loaded in-memory descendants (i.e. child associations)\n    include_descendants: false\n\n    # ......................................................................................................\n    # The max depth (number) of loaded in-memory descendants to include when `include_descendants == true`\n    # For example, a value of (2) would include the following:\n    #   Parent \u003e Child \u003e Grandchild\n    descendant_depth: 0\n```\n\nOptions can be applied whenever creating a UID.\n\n```ruby\nhash = { a: 1, b: 2, c: 3 }\n\nuri = URI::UID.build(hash, exclude: [:b]).to_s\n#=\u003e \"uid://universalid/CwSAgtQAYQHUAGMDAw#CwSAkccFf6RIYXNoAw\"\n\nuid = URI::UID.parse(uri)\n#=\u003e #\u003cURI::UID payload=CwSAgtQAYQHUAGMDAw, fingerprint=CwSAkccFf6RIYXNoAw\u003e\n\nuid.decode\n#=\u003e {:a=\u003e1, :c=\u003e3}\n```\n\n\u003e [!NOTE]\n\u003e Options can be passed in structured or flat format.\n\nIt's also possible to register frequently used options.\n\n```yaml\n# app/config/changed.yml\nprepack:\n  include_blank: false\n\n  database:\n    include_changes: true\n    include_descendants: true\n    descendant_depth: 2\n```\n\n```ruby\nUniversalID::Settings.register :changed, File.expand_path(\"app/config/changed.yml\", __dir__)\nuid = URI::UID.build(record, UniversalID::Settings[:changed])\n```\n\n## Advanced Usage\n\n### Fingerprinting\n\nEach UID is fingerprinted as part of the serialization process.\n\nFingerprints are comprised of the following components:\n\n1. `Class (Class)`  - The encoded object's class\n2. `Timestamp (Time)` - The `mtime` (UTC) of the file that defined the object's class\n\nFingerprints provide a simple mechanism to help manage data format versions... **minimizing the need for custom versioning solutions**.\nWhenever the class definition changes, the `mtime` updates, resulting in a different fingerprint.\nThis is especially useful in scenarios where the data format evolves over time, such as in long-lived applications.\n\n```ruby\nuid = URI::UID.build(campaign)\n\nuid.fingerprint\n#=\u003e \"CwuAkscJf6hDYW1wYWlnbtf_ReuZnGWeG5MD\"\n\nuid.fingerprint(decode: true)\n#=\u003e [Campaign(id: integer, ...), \u003cTime\u003e]\n```\n\n\u003e [!NOTE]\n\u003e The timestamp or `mtime` is determined the moment a UID is created.\n\n\u003e [!TIP]\n\u003e Fingerprints can help you maintain consistency and reliability when working with serialized data over time.\n\u003e While fingerpint creation is automatic and implicit, usage is optional... ready whenever you need it.\n\n### Copy ActiveRecord Models\n\nMake a copy of an ActiveRecord model _(with loaded associations)_.\n\n```ruby\ncampaign = Campaign.first\n\n# ensure desired associations are loaded so they can be included in an UID\ncampaign.emails.load\ncampaign.emails.each { |e| e.attachments.load }\n\n# introspection\ncampaign.id #=\u003e 1\ncampaign.emails.map(\u0026:id) #=\u003e [1, 2]\ncampaign.emails.map(\u0026:attachments).flatten.map(\u0026:id)\n#=\u003e [1, 2, 3, 4]\n\n# setup options for copying\noptions = {\n  include_blank: false,\n  include_keys: false,\n  include_timestamps: false,\n  include_descendants: true,\n  descendant_depth: 2\n}\n\nuri = URI::UID.build(campaign, options).to_s\n#=\u003e \"uid://universalid/G7kAIBylMxZa7MouY3gUqHKkIx3hk4s8NT5xWwQsDc7lKUkGWM4DHsCxQZK...\"\n\nuid = URI::UID.parse(uri)\n#=\u003e #\u003cURI::UID payload=G7kAIBylMxZa7MouY3gUqHKkIx3hk4s8NT5xW..., fingerprint=CwuAkscJf6hDYW1wYWlnbtf_ReuZnGWeG5MD\u003e\n\ncopy = uid.decode\n#=\u003e #\u003cCampaign:0x00000001135c7448 id: nil, name: \"My Campaign\", ...\u003e\n\ncopy == campaign\n#=\u003e false\n\n# introspection\ncopy.new_record? #=\u003e true\ncopy.id #=\u003e nil\ncopy.emails.map(\u0026:id) #=\u003e [nil, nil]\ncopy.emails.map(\u0026:attachments).flatten.map(\u0026:id)\n#=\u003e [nil, nil, nil, nil]\n\n# create the copy (new records) in the database\ncopy.save #=\u003e true\n```\n\n\u003e [!TIP]\n\u003e If you don't need a URL-Safe UID, you can use `UniversalID::Packer` to speed things up a bit.\n\n```ruby\npacked = UniversalID::Packer.pack(campaign, options)\ncopy = UniversalID::Packer.unpack(packed)\ncopy.save\n```\n\n### ActiveRecord::Relations\n\nUniversal ID also supports ActiveRecord relations/scopes.\nYou can easily serialize complex queries into a portable and sharable format.\n\n```ruby\nrelation = Campaign.joins(:emails).where(\"emails.subject LIKE ?\", \"Flash Sale%\")\n\nuri = URI::UID.build(relation).to_s\n#=\u003e \"uid://universalid/G90EQCwLeEP1oQtHFksrdN5YS4ju5TryFZwBJgh2toqS3SKEVSl1FoNtZjI...\"\n\nuid = URI::UID.parse(encoded)\n#=\u003e #\u003cURI::UID payload=G90EQCwLeEP1oQtHFksrdN5YS4ju5TryFZwBJ..., fingerprint=CxKAkscXf7ZBY3RpdmVSZWNvcmQ6OlJlbGF0a...\u003e\n\ndecoded = uid.decode\n\n# introspection\ndecoded == relation #=\u003e true\ndecoded.is_a? ActiveRecord::Relation #=\u003e true\ndecoded.loaded? #=\u003e false\n\n# run the query\ncampaigns = decoded.load\n```\n\n\u003e [!NOTE]\n\u003e Universal ID clears cached data within the relation before encoding. This minimizes payload size while preserving the integrity of the underlying query.\n\n### SignedGlobalID\n\nFeatures like `signing` _(to prevent tampering)_, `purpose`, and `expiration` are provided by SignedGlobalIDs.\nThese features _(and more)_ will eventually be added to Universal ID, but until then...\nsimply convert your UID to a SignedGlobalID to add these features to any Universal ID.\n\n```ruby\ndata = OpenStruct.new(name: \"Demo\", value: \"Example\")\n\nsgid = URI::UID.build(data).to_sgid_param(for: \"purpose\", expires_in: 1.hour)\n#=\u003e \"eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaEpJZ0plQTJkcFpEb3ZMM1Z1YVhabGNuTmhiQzFwWkM5V...\"\n\nuid = URI::UID.from_sgid(sgid, for: \"purpose\")\n#=\u003e #\u003cURI::UID payload=Cw-Axxx-gtYAbmFtZaREZW1vxwUAdmFsdWWnR..., fingerprint=ixqAkscof9kmVW5pdmVyc2FsSUQ6OkV4dGVuc...\u003e\n\ndecoded = uid.decode\n#=\u003e #\u003cOpenStruct name=\"Demo\", value=\"Example\"\u003e\n\n# a mismatched purpose returns nil... as expected\nURI::UID.from_sgid(sgid, for: \"mismatch\")\n#=\u003e nil\n```\n\n## Sponsors\n\n\u003cp align=\"center\"\u003e\n  \u003cem\u003eProudly sponsored by\u003c/em\u003e\n\u003c/p\u003e\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://www.clickfunnels.com?utm_source=hopsoft\u0026utm_medium=open-source\u0026utm_campaign=universalid\"\u003e\n    \u003cimg src=\"https://images.clickfunnel.com/uploads/digital_asset/file/176632/clickfunnels-dark-logo.svg\" width=\"575\" /\u003e\n  \u003c/a\u003e\n\u003c/p\u003e\n\n[Add your company...](https://github.com/sponsors/hopsoft/sponsorships?sponsor=hopsoft\u0026tier_id=23918\u0026preview=false)\n\n## License\n\nThe gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).\n","funding_links":["https://github.com/sponsors/hopsoft","https://github.com/sponsors/hopsoft/sponsorships?sponsor=hopsoft\u0026tier_id=23918\u0026preview=false"],"categories":["Ruby"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhopsoft%2Funiversalid","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhopsoft%2Funiversalid","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhopsoft%2Funiversalid/lists"}