{"id":13879344,"url":"https://github.com/tubbo/active_record-embedded","last_synced_at":"2025-04-12T01:17:59.781Z","repository":{"id":139665191,"uuid":"151899951","full_name":"tubbo/active_record-embedded","owner":"tubbo","description":"Embed Data in your ActiveRecord Models","archived":false,"fork":false,"pushed_at":"2023-12-15T08:19:58.000Z","size":247,"stargazers_count":5,"open_issues_count":1,"forks_count":2,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-04-12T01:17:53.969Z","etag":null,"topics":["activerecord","document-database","rails","ruby"],"latest_commit_sha":null,"homepage":"https://www.rubydoc.info/github/tubbo/active_record-embedded","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/tubbo.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}},"created_at":"2018-10-07T02:40:28.000Z","updated_at":"2024-03-30T09:53:43.000Z","dependencies_parsed_at":"2024-11-24T08:31:27.209Z","dependency_job_id":"e2b0ef11-2951-4f8b-86b6-04d4f9a36cc1","html_url":"https://github.com/tubbo/active_record-embedded","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tubbo%2Factive_record-embedded","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tubbo%2Factive_record-embedded/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tubbo%2Factive_record-embedded/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tubbo%2Factive_record-embedded/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tubbo","download_url":"https://codeload.github.com/tubbo/active_record-embedded/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248501856,"owners_count":21114684,"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":["activerecord","document-database","rails","ruby"],"created_at":"2024-08-06T08:02:17.735Z","updated_at":"2025-04-12T01:17:59.761Z","avatar_url":"https://github.com/tubbo.png","language":"Ruby","funding_links":[],"categories":["Ruby"],"sub_categories":[],"readme":"# ActiveRecord::Embedded\n\n[![Build Status](https://travis-ci.org/tubbo/active_record-embedded.svg?branch=master)](https://travis-ci.org/tubbo/active_record-embedded)\n[![Maintainability](https://api.codeclimate.com/v1/badges/b0ff9f3ab10969a1e4c2/maintainability)](https://codeclimate.com/github/tubbo/active_record-embedded/maintainability)\n[![Test Coverage](https://api.codeclimate.com/v1/badges/b0ff9f3ab10969a1e4c2/test_coverage)](https://codeclimate.com/github/tubbo/active_record-embedded/test_coverage)\n\nEmbed data in your ActiveRecord models.\n\nFor more information, check out the [API Documentation][]\n\n## Installation\n\nAdd this line to your application's Gemfile:\n\n```ruby\ngem 'active_record-embedded'\n```\n\nThen, run the following command:\n\n```bash\n$ bundle\n```\n\n## Usage\n\nCreate a new model in **app/models/item.rb**:\n\n```ruby\nclass Item\n  include ActiveRecord::Embedded::Model\n\n  embedded_in :order\n\n  field :sku\n  field :quantity, type: Integer\n  field :customizations, type: Hash\n  field :price, type: Float\n  field :discounts, type: Array\nend\n```\n\nEmbed it in an existing model with a field `:items`:\n\n```ruby\nclass Order \u003c ApplicationRecord\n  embeds_many :items\nend\n```\n\nMake sure to generate a migration for storing this data. Use the best\ndata type that your database provides for storing schema-less data. In\nPostgreSQL, that's most likely [jsonb][]:\n\n```bash\n$ rails generate migration AddItemsToOrder items:jsonb\n$ rails db:migrate\n```\n\nIf your database doesn't support storing schema-less data, you can\nstore data in a regular String datatype:\n\n```bash\n$ rails generate migration AddItemsToOrder items\n$ rails db:migrate\n```\n\nThen, configure `ActiveRecord::Embedded` to serialize embedded data in\nan initializer:\n\n```ruby\nActiveRecord::Embedded.config.serialize_data = true\n```\n\n### Querying\n\nEmbedded relations can be queried like any other model.\n\n```ruby\n# Find an embedded model by its ID\n@order.items.find('b05845e7-cb6b-4bc2-aa45-0361189929d0') # =\u003e \u003cItem ...\u003e\n\n# Find a model by one of its attributes\n@order.items.find_by(sku: 'SKU123') # =\u003e \u003cItem ...\u003e\n\n# Find all items with a quantity of 1\n@order.items.where(quantity: 1) # =\u003e \u003cActiveRecord::Embedded::Relation ...\u003e\n\n# Sort items by their SKU\n@order.items.order(sku: :desc) # =\u003e \u003cActiveRecord::Embedded::Relation ...\u003e\n```\n\nData is lazy-loaded, meaning the query on the original model is not\nrun until data is requested. It is thereby casted into the model\nclass you defined for it, and returned:\n\n```ruby\nitems = @order.items.where(sku: 'SKU123') # =\u003e \u003cActiveRecord::Embedded::Relation ...\u003e\nitems = items.order(created_at: :desc) # =\u003e \u003cActiveRecord::Embedded::Relation ...\u003e\nitems.map { |item| item } # =\u003e \u003cArray\u003cItem\u003e\u003e\n```\n\n#### Aggregation Queries\n\nAggregations are queries performed on an entire table of records, rather\nthan just the embedded data within a single record. Aggregations can be\naccessed using the familiar ActiveRecord querying API:\n\n```ruby\n# Filter by key/value pairs\nItem.where(sku: 'SKU1')\n\n# Sort by fields with a given direction\nitems = Item.order(quantity: :asc)\n\n# Return a maximum of 10 items\nitems.limit(10)\n\n# Start at the 2nd item\nitems.offset(2)\n```\n\nThese methods are actually just syntax sugar for the `.aggregate`\nmethod, which can be used to construct custom queries without needing to\nchain method calls:\n\n```ruby\nItem.aggregate(start: 2, limit: 8)\n```\n\nAggregation queries are aided by your ActiveRecord adapter's driver, if\none exists. Otherwise, the \"native\" adapter is used which uses Ruby to\niterate through all records. The following databases are supported:\n\n- [PostgreSQL][postgres-driver] (requires [PostgreSQL 9.3][postgres])\n- [MySQL (WIP)][mysql-driver] (requires [MySQL 5.7.8][mysql])\n- [SQLite3 w/JSON1 Extension (WIP)][sqlite-driver] (requires the [JSON1][] extension)\n- SQL Server (planned)\n- Oracle (planned)\n\nThe goal is to support all databases with ActiveRecord adapters that\nsupport JSON as a native data type.\n\n### Assignment\n\nEmbedded relations are assigned in a similar way to ActiveRecord's API:\n\n```ruby\n# The preferred way to create embedded relations is off their parent\n@order.items.create(quantity: 1, sku: 'SKU123') # =\u003e \u003cItem...\u003e\n\n# You can also create them in the constructor...\nitem = Item.new(quantity: 1, sku: 'SKU456') # =\u003e \u003cItem...\u003e\n\n# However, this won't save until attached to an Order:\nitem.save # =\u003e false\nitem.order = @order\nitem.save # =\u003e true\n\n# Assign your parent model in the constructor, using the name given in `embedded_in`...\nItem.new(quantity: 1, sku: 'SKU456', order: @order) # =\u003e \u003cItem...\u003e\n\n# ...or, the global `:parent` attribute:\nItem.new(quantity: 1, sku: 'SKU456', parent: @order) # =\u003e \u003cItem...\u003e\n```\n\nMethods such as `create_#{assocation}` and `destroy_#{assocation}` are\nprovided for singular relationships, just like in ActiveRecord's\n`has_one` association.\n\nConsider a `User` which embeds address data:\n\n```ruby\n# app/models/user.rb\nclass User \u003c ApplicationRecord\n  embeds_one :address\nend\n```\n\nAnd an `Address` for modeling the embedded data:\n\n```ruby\n# app/models/address.rb\nclass Address\n  include ActiveRecord::Embedded::Model\n\n  embedded_in :user\n\n  field :name\n  field :street_1\n  field :street_2\n  field :city\n  field :region\n  field :country\n\n  validates :name, presence: true\n  validates :street_1, presence: true\n  validates :city, presence: true\n  validates :region, presence: true\n  validates :country, presence: true\nend\n```\n\nOne can create the address like so, since `@user.address.create` will\nthrow an error.\n\n```ruby\n@user.address # =\u003e nil\naddress = @user.create_address(\n  name: 'Lester Tester',\n  street_1: '123 Fake street',\n  city: 'Fakeadelphia',\n  region: 'FA',\n  country: 'US'\n)\naddress # =\u003e \u003cAddress ...\u003e\n```\n\n### Field Types\n\nFields must correspond to the standard JSON types, as defined by\n[RFC 7159][], the most recent standard specification as of this writing.\n\nThese types are:\n\n- `object`, in Ruby represented as [Hash][]\n- `array`, represented in Ruby as an [Array][]\n- `number`, which can either be represented as an [Integer][] or [Float][]\n- `string`, represented in Ruby as a regular [String][] class\n- `boolean`, represented by the [true][] and [false][] literals in Ruby and...\n- `null`, which is represented in Ruby as [nil][]\n\nThe above list represents a total catalog of all types that can be\neventually stored into the database, but additional objects are\nrepresented by custom types...\n\n#### Custom Types\n\nCustom types are supported by subclassing\n`ActiveRecord::Embedded::Field`. This is an interface that requires the\nimplementation of a `#cast` method in order to provide typecasting\nfunctionality for a complex type. Custom types \"boil down\" a complex\ntype defined in Ruby into something that can be serialized to a\nprimitive JSON type, typically a Hash.\n\nHere's an example of a custom type for the [Money][] object, defined in\n**lib/active_record/embedded/field/money.rb** in your Rails application:\n\n```ruby\nmodule ActiveRecord\n  module Embedded\n    class Field\n      class Money \u003c self\n        # This method is called to prepare the field for insertion into\n        # the database. It must return one of the standard JSON types.\n        # In this example, a Hash is returned.\n        def cast(value)\n          {\n            '$cents' =\u003e value.cents,\n            '$currency' =\u003e value.currency\n          }\n        end\n\n        # When a value is being pulled out of the database, this is the\n        # method called to convert its value back into that of the\n        # higher-level field type. Since the #cast method converts this\n        # type into a Hash, access is granted to the currency and cents\n        # of the given object.\n        def coerce(value = nil)\n          return if value.blank?\n          ::Money.new(value['$cents'], value['$currency'])\n        end\n      end\n    end\n  end\nend\n```\n\nYou can require this file in your **config/application.rb**:\n\n```ruby\nrequire 'active_record/embedded/field/money'\n```\n\nBy doing so, the `Money` type will be available to your embedded models:\n\n```ruby\nclass Item\n  include ActiveRecord::Embedded::Model\n\n  embedded_in :order\n\n  field :price, type: Money\nend\n```\n\n### Indexing\n\nIndexes on known queries help to speed up reading embedded data from the\ndatabase, especially when dealing with a large amount of records.\n\nTo define an index on an embedded model, use the `index` macro:\n\n```ruby\nclass Item\n  include ActiveRecord::Embedded::Model\n\n  embedded_in :order\n\n  field :sku, type: String\n\n  index :sku, unique: true\nend\n```\n\nThis macro is based off of Mongoid's, but doesn't include the esoteric\nsyntax of MongoDB. Instead, you provide the attributes you wish to index\n(an Array can be specified if it's a compound index), then the options\nfor said index. The options for indexes are as follows:\n\n- `:direction` can be `:asc` (default) or `:desc`\n- `:unique` if set to `true` will throw an error when a non-unique value\n  is added to the index\n\n### Rails Integration\n\nAlthough Rails isn't required to use this library, some out-of-box\nfunctionality is included into the model in case it is within a Rails\napp. You'll find that the generated `*_path`, `*_url` and of course the\n`url_for` helpers will generate predictable path names for your embedded\nmodels, and `#cache_key` has been modified to include the parent model's\ncache key for easy manual expiration.\n\n## Contributing\n\nAll contributions to this library are welcome and encouraged. Please submit\na [pull request][] for changes to documentation or source, and if you see an\nissue, please [report it][]! Please make sure you're familiar with the\n[code of conduct][] when contributing.\n\n### Running Tests\n\nTo run tests, make sure you have a database set up (you only have to do\nthis once):\n\n```bash\n$ rails app:db:setup\n```\n\nRun all tests and RuboCop lint checks with the following command:\n\n```bash\n$ rails lint test\n```\n\n## License\n\nThe gem is available as open source under the terms of the [MIT License][].\n\n[pull request]: https://github.com/tubbo/active-record_embedded/pulls\n[report it]: https://github.com/tubbo/active-record_embedded/issues/new\n[MIT License]: https://opensource.org/licenses/MIT\n[code of conduct]: https://www.contributor-covenant.org/version/1/4/code-of-conduct\n[API Documentation]: https://www.rubydoc.info/github/tubbo/active-record_embedded/latest\n[jsonb]: https://www.postgresql.org/docs/9.4/static/datatype-json.html\n[RFC 7159]: https://tools.ietf.org/html/rfc7159.html#section-3\n[Hash]: https://ruby-doc.org/core-2.5.1/Hash.html\n[Array]: http://ruby-doc.org/core-2.5.1/Array.html\n[Integer]: http://ruby-doc.org/core-2.5.1/Integer.html\n[Float]: http://ruby-doc.org/core-2.5.1/Float.html\n[String]: http://ruby-doc.org/core-2.5.1/String.html\n[true]: http://ruby-doc.org/core-2.5.1/TrueClass.html\n[false]: http://ruby-doc.org/core-2.5.1/FalseClass`.html\n[nil]: http://ruby-doc.org/core-2.5.1/NilClass.html\n[Money]: http://rubymoney.github.io/money/\n[postgres-driver]: https://www.rubydoc.info/github/tubbo/active_record-embedded/ActiveRecord/Embedded/Aggregation/Postgresql\n[mysql-driver]: https://github.com/tubbo/active_record-embedded/tree/mysql-driver\n[sqlite-driver]: https://github.com/tubbo/active_record-embedded/tree/sqlite-driver\n[postgres]: https://www.postgresql.org/docs/9.3/static/functions-json.html\n[mysql]: https://dev.mysql.com/doc/relnotes/mysql/5.7/en/news-5-7-8.html#mysqld-5-7-8-json\n[JSON1]: https://www.sqlite.org/json1.html\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftubbo%2Factive_record-embedded","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftubbo%2Factive_record-embedded","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftubbo%2Factive_record-embedded/lists"}