{"id":13528199,"url":"https://github.com/Dynamoid/dynamoid","last_synced_at":"2025-04-01T11:31:06.691Z","repository":{"id":31182452,"uuid":"34742968","full_name":"Dynamoid/dynamoid","owner":"Dynamoid","description":"Ruby ORM for Amazon's DynamoDB. ","archived":false,"fork":false,"pushed_at":"2025-03-14T20:23:44.000Z","size":27466,"stargazers_count":588,"open_issues_count":78,"forks_count":196,"subscribers_count":23,"default_branch":"master","last_synced_at":"2025-03-23T22:45:45.054Z","etag":null,"topics":["aws-dynamodb","dynamodb","hacktoberfest","orm","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/Dynamoid.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":["Dynamoid","pboling"],"patreon":null,"open_collective":"dynamoid","ko_fi":null,"tidelift":"rubygems/dynamoid","community_bridge":null,"liberapay":null,"issuehunt":null,"otechie":null,"lfx_crowdfunding":null,"custom":null}},"created_at":"2015-04-28T16:39:36.000Z","updated_at":"2025-02-27T21:16:59.000Z","dependencies_parsed_at":"2023-11-08T00:32:42.105Z","dependency_job_id":"daf164c5-5d1d-46d3-b592-5cc88f228b25","html_url":"https://github.com/Dynamoid/dynamoid","commit_stats":{"total_commits":1364,"total_committers":121,"mean_commits":"11.272727272727273","dds":0.5857771260997067,"last_synced_commit":"d0662dbaab0fef0219d2c109f95b70b4762e961f"},"previous_names":[],"tags_count":45,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Dynamoid%2Fdynamoid","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Dynamoid%2Fdynamoid/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Dynamoid%2Fdynamoid/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Dynamoid%2Fdynamoid/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Dynamoid","download_url":"https://codeload.github.com/Dynamoid/dynamoid/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246481004,"owners_count":20784458,"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":["aws-dynamodb","dynamodb","hacktoberfest","orm","ruby"],"created_at":"2024-08-01T06:02:17.725Z","updated_at":"2025-04-01T11:31:06.682Z","avatar_url":"https://github.com/Dynamoid.png","language":"Ruby","readme":"# Dynamoid\n\n[![Gem Version][⛳️version-img]][⛳️gem]\n[![Supported Build Status][🏘sup-wf-img]][🏘sup-wf]\n[![Maintainability][⛳cclim-maint-img♻️]][⛳cclim-maint]\n[![Coveralls][🏘coveralls-img]][🏘coveralls]\n[![CodeCov][🖇codecov-img♻️]][🖇codecov]\n[![Helpers][🖇triage-help-img]][🖇triage-help]\n[![Contributors][🖐contributors-img]][🖐contributors]\n[![RubyDoc.info][🚎yard-img]][🚎yard]\n[![License][🖇src-license-img]][🖇src-license]\n[![GitMoji][🖐gitmoji-img]][🖐gitmoji]\n[![SemVer 2.0.0][🧮semver-img]][🧮semver]\n[![Keep-A-Changelog 1.0.0][📗keep-changelog-img]][📗keep-changelog]\n[![Sponsor Project][🖇sponsor-img]][🖇sponsor]\n\nDynamoid is an ORM for Amazon's DynamoDB for Ruby applications. It\nprovides similar functionality to ActiveRecord and improves on Amazon's\nexisting\n[HashModel](http://docs.amazonwebservices.com/AWSRubySDK/latest/AWS/Record/HashModel.html)\nby providing better searching tools and native association support.\n\nDynamoDB is not like other document-based databases you might know, and\nis very different indeed from relational databases. It sacrifices\nanything beyond the simplest relational queries and transactional\nsupport to provide a fast, cost-efficient, and highly durable storage\nsolution. If your database requires complicated relational queries\nthen this modest Gem cannot provide them for you\nand neither can DynamoDB. In those cases you would do better to look\nelsewhere for your database needs.\n\nBut if you want a fast, scalable, simple, easy-to-use database (and a\nGem that supports it) then look no further!\n\n## Installation\n\nInstalling Dynamoid is pretty simple. First include the Gem in your\nGemfile:\n\n```ruby\ngem 'dynamoid'\n```\n## Prerequisites\n\nDynamoid depends on the aws-sdk, and this is tested on the current\nversion of aws-sdk (~\u003e 3), rails (\u003e= 4). Hence the configuration as\nneeded for aws to work will be dealt with by aws setup.\n\n### AWS SDK Version Compatibility\n\nMake sure you are using the version for the right AWS SDK.\n\n| Dynamoid version | AWS SDK Version |\n| ---------------- | --------------- |\n| 0.x              | 1.x             |\n| 1.x              | 2.x             |\n| 2.x              | 2.x             |\n| 3.x              | 3.x             |\n\n### AWS Configuration\n\nConfigure AWS access:\n[Reference](https://github.com/aws/aws-sdk-ruby)\n\nFor example, to configure AWS access:\n\nCreate `config/initializers/aws.rb` as follows:\n\n```ruby\nAws.config.update(\n  region: 'us-west-2',\n  credentials: Aws::Credentials.new('REPLACE_WITH_ACCESS_KEY_ID', 'REPLACE_WITH_SECRET_ACCESS_KEY'),\n)\n```\n\nAlternatively, if you don't want Aws connection settings to be\noverwritten for you entire project, you can specify connection settings\nfor Dynamoid only, by setting those in the `Dynamoid.configure` clause:\n\n```ruby\nrequire 'dynamoid'\nDynamoid.configure do |config|\n  config.access_key = 'REPLACE_WITH_ACCESS_KEY_ID'\n  config.secret_key = 'REPLACE_WITH_SECRET_ACCESS_KEY'\n  config.region = 'us-west-2'\nend\n```\n\nAdditionally, if you would like to pass in pre-configured AWS credentials\n(e.g. you have an IAM role credential, you configure your credentials\nelsewhere in your project, etc.), you may do so:\n\n```ruby\nrequire 'dynamoid'\n\ncredentials = Aws::AssumeRoleCredentials.new(\n  region: region,\n  access_key_id: key,\n  secret_access_key: secret,\n  role_arn: role_arn,\n  role_session_name: 'our-session'\n)\n\nDynamoid.configure do |config|\n  config.region = 'us-west-2',\n                  config.credentials = credentials\nend\n```\n\nFor a full list of the DDB regions, you can go\n[here](http://docs.aws.amazon.com/general/latest/gr/rande.html#ddb_region).\n\nThen you need to initialize Dynamoid config to get it going. Put code\nsimilar to this somewhere (a Rails initializer would be a great place\nfor this if you're using Rails):\n\n```ruby\nrequire 'dynamoid'\nDynamoid.configure do |config|\n  # To namespace tables created by Dynamoid from other tables you might have.\n  # Set to nil to avoid namespacing.\n  config.namespace = 'dynamoid_app_development'\n\n  # [Optional]. If provided, it communicates with the DB listening at the endpoint.\n  # This is useful for testing with [DynamoDB Local] (http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Tools.DynamoDBLocal.html).\n  config.endpoint = 'http://localhost:3000'\nend\n```\n\n### Ruby \u0026 Rails Compatibility\n\nDynamoid supports Ruby \u003e= 2.3 and Rails \u003e= 4.2.\n\nIts compatibility is tested against following Ruby versions: 2.3, 2.4,\n2.5, 2.6, 2.7, 3.0, 3.1, 3.2, 3.3, and 3.4, JRuby 9.4.x and against Rails versions: 4.2, 5.0, 5.1,\n5.2, 6.0, 6.1, 7.0, 7.1, 7.2, and 8.0.\n\n## Setup\n\nYou *must* include `Dynamoid::Document` in every Dynamoid model.\n\n```ruby\nclass User\n  include Dynamoid::Document\n\n  # fields declaration\nend\n```\n\n### Table\n\nDynamoid has some sensible defaults for you when you create a new table,\nincluding the table name and the primary key column. But you can change\nthose if you like on table creation.\n\n```ruby\nclass User\n  include Dynamoid::Document\n\n  table name: :awesome_users, key: :user_id, read_capacity: 5, write_capacity: 5\nend\n```\n\nThese fields will not change an existing table: so specifying a new\nread_capacity and write_capacity here only works correctly for entirely\nnew tables. Similarly, while Dynamoid will look for a table named\n`awesome_users` in your namespace, it won't change any existing tables\nto use that name; and if it does find a table with the correct name, it\nwon't change its hash key, which it expects will be `user_id`. If this\ntable doesn't exist yet, however, Dynamoid will create it with these\noptions.\n\nThere is a basic support of DynamoDB's [Time To Live (TTL)\nmechanism](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/TTL.html).\nIf you declare a field as TTL field - it will be initialised if doesn't\nhave value yet. Default value is current time + specified seconds.\n\n```ruby\nclass User\n  include Dynamoid::Document\n\n  table expires: { field: :ttl, after: 60 }\n\n  field :ttl, :integer\nend\n```\n\nField used to store expiration time (e.g. `ttl`) should be declared\nexplicitly and should have numeric type (`integer`, `number`) only.\n`datetime` type is also possible but only if it's stored as number\n(there is a way to store time as a string also).\n\nIt's also possible to override a global option `Dynamoid::Config.timestamps`\non a table level:\n\n```ruby\ntable timestamps: false\n```\n\nThis option controls generation of timestamp fields\n`created_at`/`updated_at`.\n\nIt's also possible to override table capacity mode configured globally\nwith table level option `capacity_mode`. Valid values are\n`:provisioned`, `:on_demand` and `nil`:\n\n```ruby\ntable capacity_mode: :on_demand\n```\n\nIf table capacity mode is on-demand, another related table-level options\n`read_capacity` and `write_capacity` will be ignored.\n\n### Fields\n\nYou'll have to define all the fields on the model and the data type of\neach field. Every field on the object must be included here; if you miss\nany they'll be completely bypassed during DynamoDB's initialization and\nwill not appear on the model objects.\n\nBy default, fields are assumed to be of type `string`. Other built-in\ntypes are `integer`, `number`, `set`, `array`, `map`, `datetime`,\n`date`, `boolean`, `binary`, `raw` and `serialized`. `array` and\n`map` match List and Map DynamoDB types respectively. `raw` type means\nyou can store Ruby Array, Hash, String and numbers. If built-in types do\nnot suit you, you can use a custom field type represented by an\narbitrary class, provided that the class supports a compatible\nserialization interface.  The primary use case for using a custom field\ntype is to represent your business logic with high-level types, while\nensuring portability or backward-compatibility of the serialized\nrepresentation.\n\n#### Note on boolean type\n\nThe boolean fields are stored as DynamoDB boolean values by default.\nDynamoid can store boolean values as strings as well - `'t'` and `'f'`.\nSo if you want to change the default format of boolean field you can\neasily achieve this with `store_as_native_boolean` field option:\n\n```ruby\nclass Document\n  include Dynamoid::Document\n\n  field :active, :boolean, store_as_native_boolean: false\nend\n```\n\n#### Note on date type\n\nBy default date fields are persisted as days count since 1 January 1970\nlike UNIX time. If you prefer dates to be stored as ISO-8601 formatted\nstrings instead then set `store_as_string` to `true`\n\n```ruby\nclass Document\n  include Dynamoid::Document\n\n  field :sent_on, :date, store_as_string: true\nend\n```\n\n#### Note on datetime type\n\nBy default datetime fields are persisted as UNIX timestamps with\nmillisecond precision in DynamoDB. If you prefer datetimes to be stored\nas ISO-8601 formatted strings instead then set `store_as_string` to\n`true`\n\n```ruby\nclass Document\n  include Dynamoid::Document\n\n  field :sent_at, :datetime, store_as_string: true\nend\n```\n\n**WARNING:** Fields in numeric format are stored with nanoseconds as a\nfraction part and precision could be lost. That's why `datetime` field\nin numeric format shouldn't be used as a range key.\n\nYou have two options if you need to use a `datetime` field as a range\nkey:\n* string format\n* store `datetime` values without milliseconds (e.g. cut\n  them manually with `change` method - `Time.now.change(usec: 0)`\n\n#### Note on set type\n\n`Dynamoid`'s type `set` is stored as DynamoDB's Set attribute type.\nDynamoDB supports only Set of strings, numbers and binary. Moreover Set\n*must* contain elements of the same type only.\n\nIn order to use some other `Dynamoid`'s types you can specify `of`\noption to declare the type of set elements.\n\nAs a result of that DynamoDB limitation, in Dynamoid only the following\nscalar types are supported (note: does not support `boolean`):\n`integer`, `number`, `date`, `datetime`, `serializable` and custom\ntypes.\n\n```ruby\nclass Document\n  include Dynamoid::Document\n\n  field :tags, :set, of: :integer\nend\n```\n\nIt's possible to specify field options like `store_as_string` for\n`datetime` field or `serializer` for `serializable` field for `set`\nelements type:\n\n```ruby\nclass Document\n  include Dynamoid::Document\n\n  field :values, :set, of: { serialized: { serializer: JSON } }\n  field :dates, :set, of: { date: { store_as_string: true } }\n  field :datetimes, :set, of: { datetime: { store_as_string: false } }\nend\n```\n\nDynamoDB doesn't allow empty strings in fields configured as `set`.\nAbiding by this restriction, when `Dynamoid` saves a document it removes\nall empty strings in set fields.\n\n#### Note on array type\n\n`Dynamoid`'s type `array` is stored as DynamoDB's List attribute type.\nIt can contain elements of different types (in contrast to Set attribute\ntype).\n\nIf you need to store in array field elements of `datetime`, `date`,\n`serializable` or some custom type, which DynamoDB doesn't support\nnatively, you should specify element type with `of` option:\n\n```ruby\nclass Document\n  include Dynamoid::Document\n\n  field :dates, :array, of: :date\nend\n```\n\n#### Note on binary type\n\nBy default binary fields are persisted as DynamoDB String value encoded\nin the Base64 encoding. DynamoDB supports binary data natively. To use\nit instead of String a `store_binary_as_native` field option should be\nset:\n\n```ruby\nclass Document\n  include Dynamoid::Document\n\n  field :image, :binary, store_binary_as_native: true\nend\n```\n\nThere is also a global config option `store_binary_as_native` that is\n`false` by default as well.\n\n#### Magic Columns\n\nYou get magic columns of `id` (`string`), `created_at` (`datetime`), and\n`updated_at` (`datetime`) for free.\n\n```ruby\nclass User\n  include Dynamoid::Document\n\n  field :name\n  field :email\n  field :rank, :integer\n  field :number, :number\n  field :joined_at, :datetime\n  field :hash, :serialized\nend\n```\n\n#### Default Values\n\nYou can optionally set a default value on a field using either a plain\nvalue or a lambda:\n\n```ruby\nfield :actions_taken, :integer, default: 0\nfield :joined_at, :datetime, default: -\u003e { Time.now }\n```\n\n#### Aliases\n\nIt might be helpful to define an alias for already existing field when\nnaming convention used for a table differs from conventions common in\nRuby:\n\n```ruby\nfield firstName, :string, alias: :first_name\n```\n\nThis way there will be generated\nsetters/getters/`\u003cname\u003e?`/`\u003cname\u003e_before_type_cast` methods for both\noriginal field name (`firstName`) and an alias (`first_name`).\n\n```ruby\nuser = User.new(first_name: 'Michael')\nuser.first_name # =\u003e 'Michael'\nuser.firstName # =\u003e 'Michael'\n```\n\n#### Custom Types\n\nTo use a custom type for a field, suppose you have a `Money` type.\n\n```ruby\nclass Money\n  # ... your business logic ...\n\n  def dynamoid_dump\n    'serialized representation as a string'\n  end\n\n  def self.dynamoid_load(_serialized_str)\n    # parse serialized representation and return a Money instance\n    Money.new(1.23)\n  end\nend\n\nclass User\n  include Dynamoid::Document\n\n  field :balance, Money\nend\n```\n\nIf you want to use a third-party class (which does not support\n`#dynamoid_dump` and `.dynamoid_load`) as your field type, you can use\nan adapter class providing `.dynamoid_dump` and `.dynamoid_load` class\nmethods for your third-party class. `.dynamoid_load` can remain the same\nfrom the previous example; here we just add a level of indirection for\nserializing. Example:\n\n```ruby\n# Third-party Money class\nclass Money; end\n\nclass MoneyAdapter\n  def self.dynamoid_load(_money_serialized_str)\n    Money.new(1.23)\n  end\n\n  def self.dynamoid_dump(money_obj)\n    money_obj.value.to_s\n  end\nend\n\nclass User\n  include Dynamoid::Document\n\n  field :balance, MoneyAdapter\nend\n```\n\nLastly, you can control the data type of your custom-class-backed field\nat the DynamoDB level. This is especially important if you want to use\nyour custom field as a numeric range or for number-oriented queries. By\ndefault custom fields are persisted as a string attribute, but your\ncustom class can override this with a `.dynamoid_field_type` class\nmethod, which would return either `:string` or `:number`.\n\nDynamoDB may support some other attribute types that are not yet\nsupported by Dynamoid.\n\nIf a custom type implements `#==` method you can specify `comparable:\ntrue` option in a field declaration to specify that an object is safely\ncomparable for the purpose of detecting changes. By default old and new\nobjects will be compared by their serialized representation.\n\n```ruby\nclass Money\n  # ...\n\n  def ==(other)\n    # comparison logic\n  end\nend\n\nclass User\n  # ...\n\n  field :balance, Money, comparable: true\nend\n```\n\n### Sort key\n\nAlong with partition key table may have a sort key. In order to declare\nit in a model `range` class method should be used:\n\n```ruby\nclass Post\n  include Dynamoid::Document\n\n  range :posted_at, :datetime\nend\n```\n\nSecond argument, type, is optional. Default type is `string`.\n\n### Associations\n\nJust like in ActiveRecord (or your other favorite ORM), Dynamoid uses\nassociations to create links between models.\n\n**WARNING:** Associations are not supported for models with compound\nprimary key. If a model declares a range key it should not declare any\nassociation itself and be referenced by an association in another model.\n\nThe only supported associations (so far) are `has_many`, `has_one`,\n`has_and_belongs_to_many`, and `belongs_to`. Associations are very\nsimple to create: just specify the type, the name, and then any options\nyou'd like to pass to the association. If there's an inverse association\neither inferred or specified directly, Dynamoid will update both objects\nto point at each other.\n\n```ruby\nclass User\n  include Dynamoid::Document\n\n  # ...\n\n  has_many :addresses\n  has_many :students, class: User\n  belongs_to :teacher, class_name: :user\n  belongs_to :group\n  belongs_to :group, foreign_key: :group_id\n  has_one :role\n  has_and_belongs_to_many :friends, inverse_of: :friending_users\nend\n\nclass Address\n  include Dynamoid::Document\n\n  # ...\n\n  belongs_to :user # Automatically links up with the user model\nend\n```\n\nContrary to what you'd expect, association information is always\ncontained on the object specifying the association, even if it seems\nlike the association has a foreign key. This is a side effect of\nDynamoDB's structure: it's very difficult to find foreign keys without\nan index. Usually you won't find this to be a problem, but it does mean\nthat association methods that build new models will not work correctly -\nfor example, `user.addresses.new` returns an address that is not\nassociated to the user. We'll be correcting this ~soon~ maybe someday,\nif we get a pull request.\n\n### Validations\n\nDynamoid bakes in ActiveModel validations, just like ActiveRecord does.\n\n```ruby\nclass User\n  include Dynamoid::Document\n\n  # ...\n\n  validates_presence_of :name\n  validates_format_of :email, with: /@/\nend\n```\n\nTo see more usage and examples of ActiveModel validations, check out the\n[ActiveModel validation\ndocumentation](http://api.rubyonrails.org/classes/ActiveModel/Validations.html).\n\nIf you want to bypass model validation, pass `validate: false` to `save`\ncall:\n\n```ruby\nmodel.save(validate: false)\n```\n\n### Callbacks\n\nDynamoid also employs ActiveModel callbacks. Right now the following\ncallbacks are supported:\n- `save` (before, after, around)\n- `create` (before, after, around)\n- `update` (before, after, around)\n- `validation` (before, after)\n- `destroy` (before, after, around)\n- `after_touch`\n- `after_initialize`\n- `after_find`\n\nExample:\n\n```ruby\nclass User\n  include Dynamoid::Document\n\n  # ...\n\n  before_save :set_default_password\n  after_create :notify_friends\n  after_destroy :delete_addresses\nend\n```\n\n### STI\n\nDynamoid supports STI (Single Table Inheritance) like Active Record\ndoes. You need just specify `type` field in a base class. Example:\n\n```ruby\nclass Animal\n  include Dynamoid::Document\n\n  field :name\n  field :type\nend\n\nclass Cat \u003c Animal\n  field :lives, :integer\nend\n\ncat = Cat.create(name: 'Morgan')\nanimal = Animal.find(cat.id)\nanimal.class\n#=\u003e Cat\n```\n\nIf you already have DynamoDB tables and `type` field already exists and\nhas its own semantic it leads to conflict. It's possible to tell\nDynamoid to use another field (even not existing) instead of `type` one\nwith `inheritance_field` table option:\n\n```ruby\nclass Car\n  include Dynamoid::Document\n  table inheritance_field: :my_new_type\n\n  field :my_new_type\nend\n\nc = Car.create\nc.my_new_type\n#=\u003e \"Car\"\n```\n\n### Type casting\n\nDynamoid supports type casting and tries to do it in the most convenient\nway. Values for all fields (except custom type) are coerced to declared\nfield types.\n\nSome obvious rules are used, e.g.:\n\nfor boolean field:\n```ruby\ndocument.boolean_field = 'off'\n# =\u003e false\ndocument.boolean_field = 'false'\n# =\u003e false\ndocument.boolean_field = 'some string'\n# =\u003e true\n```\n\nor for integer field:\n```ruby\ndocument.integer_field = 42.3\n# =\u003e 42\ndocument.integer_field = '42.3'\n# =\u003e 42\ndocument.integer_field = true\n# =\u003e 1\n```\n\nIf time zone isn't specified for `datetime` value - application time\nzone is used.\n\nTo access field value before type casting following method could be\nused: `attributes_before_type_cast` and\n`read_attribute_before_type_cast`.\n\nThere is `\u003cname\u003e_before_type_cast` method for every field in a model as\nwell.\n\n### Dirty API\n\nDynamoid supports Dirty API which is equivalent to [Rails 5.2\n`ActiveModel::Dirty`](https://api.rubyonrails.org/v5.2/classes/ActiveModel/Dirty.html).\nThere is only one limitation - change in place of field isn't detected\nautomatically.\n\n## Usage\n\n### Object Creation\n\nDynamoid's syntax is generally very similar to ActiveRecord's. Making\nnew objects is simple:\n\n```ruby\nu = User.new(name: 'Josh')\nu.email = 'josh@joshsymonds.com'\nu.save\n```\n\nSave forces persistence to the data store: a unique ID is also assigned,\nbut it is a string and not an auto-incrementing number.\n\n```ruby\nu.id # =\u003e '3a9f7216-4726-4aea-9fbc-8554ae9292cb'\n```\n\nTo use associations, you use association methods very similar to\nActiveRecord's:\n\n```ruby\naddress = u.addresses.create\naddress.city = 'Chicago'\naddress.save\n```\n\nTo create multiple documents at once:\n\n```ruby\nUser.create([{ name: 'Josh' }, { name: 'Nick' }])\n```\n\nThere is an efficient and low-level way to create multiple documents\n(without validation and callbacks running):\n\n```ruby\nusers = User.import([{ name: 'Josh' }, { name: 'Nick' }])\n```\n\n### Querying\n\nQuerying can be done in one of the following ways:\n\n```ruby\nAddress.find(address.id)              # Find directly by ID.\nAddress.where(city: 'Chicago').all    # Find by any number of matching criteria...\n                                      # Though presently only \"where\" is supported.\nAddress.find_by_city('Chicago')       # The same as above, but using ActiveRecord's older syntax.\n```\n\nThere is also a way to `#where` with a condition expression:\n\n```ruby\nAddress.where('city = :c', c: 'Chicago')\n```\n\nA condition expression may contain operators (e.g. `\u003c`, `\u003e=`, `\u003c\u003e`),\nkeywords (e.g. `AND`, `OR`, `BETWEEN`) and built-in functions (e.g.\n`begins_with`, `contains`) (see (documentation\n)[https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.OperatorsAndFunctions.html]\nfor full syntax description).\n\n**Warning:** Values (specified for a String condition expression) are\nsent as is so Dynamoid field types that aren't supported natively by\nDynamoDB (e.g. `datetime` and `date`) require explicit casting.\n\n**Warning:** String condition expressions will be used by DynamoDB only\nat filtering, so conditions on key attributes should be specified as a\nHash to perform Query operation instead of Scan. Don't use key\nattributes in `#where`'s String condition expressions.\n\nAnd you can also query on associations:\n\n```ruby\nu.addresses.where(city: 'Chicago').all\n```\n\nBut keep in mind Dynamoid - and document-based storage systems in\ngeneral - are not drop-in replacements for existing relational\ndatabases. The above query does not efficiently perform a conditional\njoin, but instead finds all the user's addresses and naively filters\nthem in Ruby. For large associations this is a performance hit compared\nto relational database engines.\n\n**Warning:** There is a caveat with filtering documents by `nil` value\nattribute. By default Dynamoid ignores attributes with `nil` value and\ndoesn't store them in a DynamoDB document. This behavior could be\nchanged with `store_attribute_with_nil_value` config option.\n\nIf Dynamoid ignores `nil` value attributes `null`/`not_null` operators\nshould be used in query:\n\n```ruby\nAddress.where('postcode.null': true)\nAddress.where('postcode.not_null': true)\n```\n\nIf Dynamoid keeps `nil` value attributes `eq`/`ne` operators should be\nused instead:\n\n```ruby\nAddress.where(postcode: nil)\nAddress.where('postcode.ne': nil)\n```\n\n#### Limits\n\nThere are three types of limits that you can query with:\n\n1. `record_limit` - The number of evaluated records that are returned by\n   the query.\n2. `scan_limit` - The number of scanned records that DynamoDB will look\n   at before returning.\n3. `batch_size` - The number of records requested to DynamoDB per\n   underlying request, good for large queries!\n\nUsing these in various combinations results in the underlying requests\nto be made in the smallest size possible and the query returns once\n`record_limit` or `scan_limit` is satisfied. It will attempt to batch\nwhenever possible.\n\nYou can thus limit the number of evaluated records, or select a record\nfrom which to start in order to support pagination.\n\n```ruby\nAddress.record_limit(5).start(address) # Only 5 addresses starting at `address`\n```\nWhere `address` is an instance of the model or a hash\n`{the_model_hash_key: 'value', the_model_range_key: 'value'}`. Keep in\nmind that if you are passing a hash to `.start()` you need to explicitly\ndefine all required keys in it including range keys, depending on table\nor secondary indexes signatures, otherwise you'll get an\n`Aws::DynamoDB::Errors::ValidationException` either for `Exclusive Start\nKey must have same size as table's key schema` or `The provided starting\nkey is invalid`\n\nIf you are potentially running over a large data set and this is\nespecially true when using certain filters, you may want to consider\nlimiting the number of scanned records (the number of records DynamoDB\ninfrastructure looks through when evaluating data to return):\n\n```ruby\nAddress.scan_limit(5).start(address) # Only scan at most 5 records and return what's found starting from `address`\n```\n\nFor large queries that return many rows, Dynamoid can use AWS' support\nfor requesting documents in batches:\n\n```ruby\n# Do some maintenance on the entire table without flooding DynamoDB\nAddress.batch(100).each { |addr| addr.do_some_work \u0026\u0026 sleep(0.01) }\nAddress.record_limit(10_000).batch(100).each { |addr| addr.do_some_work \u0026\u0026 sleep(0.01) } # Batch specified as part of a chain\n```\n\nThe implication of batches is that the underlying requests are done in\nthe batch sizes to make the request and responses more manageable. Note\nthat this batching is for `Query` and `Scans` and not `BatchGetItem`\ncommands.\n\n#### DynamoDB pagination\n\nAt times it can be useful to rely on DynamoDB [low-level\npagination](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Query.html#Query.Pagination)\ninstead of fixed pages sizes. Each page results in a single Query or\nScan call to DynamoDB, but returns an unknown number of records.\n\nAccess to the native DynamoDB pages can be obtained via the\n`find_by_pages` method, which yields arrays of records.\n\n```ruby\nAddress.find_by_pages do |addresses, metadata|\nend\n```\n\nEach yielded pages returns page metadata as the second argument, which\nis a hash including a key `:last_evaluated_key`. The value of this key\ncan be used for the `start` method to fetch the next page of records.\n\nThis way it can be used for instance to implement efficiently pagination\nin web-applications:\n\n```ruby\nclass UserController \u003c ApplicationController\n  def index\n    next_page = params[:next_page_token] ? JSON.parse(Base64.decode64(params[:next_page_token])) : nil\n\n    records, metadata = User.start(next_page).find_by_pages.first\n\n    render json: {\n      records: records,\n      next_page_token: Base64.encode64(metadata[:last_evaluated_key].to_json)\n    }\n  end\nend\n```\n\n#### Sort Conditions and Filters\n\nYou are able to optimize query with condition for sort key. Following\noperators are available: `gt`, `lt`, `gte`, `lte`, `begins_with`,\n`between` as well as equality:\n\n```ruby\nAddress.where(latitude: 10_212)\nAddress.where('latitude.gt': 10_212)\nAddress.where('latitude.lt': 10_212)\nAddress.where('latitude.gte': 10_212)\nAddress.where('latitude.lte': 10_212)\nAddress.where('city.begins_with': 'Lon')\nAddress.where('latitude.between': [10_212, 20_000])\n```\n\nYou are able to filter results on the DynamoDB side and specify\nconditions for non-key fields. Following additional operators are\navailable: `in`, `contains`, `not_contains`, `null`, `not_null`:\n\n```ruby\nAddress.where('city.in': %w[London Edenburg Birmingham])\nAddress.where('city.contains': ['on'])\nAddress.where('city.not_contains': ['ing'])\nAddress.where('postcode.null': false)\nAddress.where('postcode.not_null': true)\n```\n\n**WARNING:** Please take into account that `NULL` and `NOT_NULL`\noperators check attribute presence in a document, not value. So if\nattribute `postcode`'s value is `NULL`, `NULL` operator will return\nfalse because attribute exists even if has `NULL` value.\n\n#### Selecting some specific fields only\n\nIt could be done with `project` method:\n\n```ruby\nclass User\n  include Dynamoid::Document\n  field :name\nend\n\nUser.create(name: 'Alex')\nuser = User.project(:name).first\n\nuser.id         # =\u003e nil\nuser.name       # =\u003e 'Alex'\nuser.created_at # =\u003e nil\n```\n\nReturned models with have filled specified fields only.\n\nSeveral fields could be specified:\n\n```ruby\nuser = User.project(:name, :created_at)\n```\n\n### Consistent Reads\n\nQuerying supports consistent reading. By default, DynamoDB reads are\neventually consistent: if you do a write and then a read immediately\nafterwards, the results of the previous write may not be reflected. If\nyou need to do a consistent read (that is, you need to read the results\nof a write immediately) you can do so, but keep in mind that consistent\nreads are twice as expensive as regular reads for DynamoDB.\n\n```ruby\nAddress.find(address.id, consistent_read: true)  # Find an address, ensure the read is consistent.\nAddress.where(city: 'Chicago').consistent.all    # Find all addresses where the city is Chicago, with a consistent read.\n```\n\n### Range Finding\n\nIf you have a range index, Dynamoid provides a number of additional\nother convenience methods to make your life a little easier:\n\n```ruby\nUser.where('created_at.gt': DateTime.now - 1.day).all\nUser.where('created_at.lt': DateTime.now - 1.day).all\n```\n\nIt also supports `gte` and `lte`. Turning those into symbols and\nallowing a Rails SQL-style string syntax is in the works. You can only\nhave one range argument per query, because of DynamoDB inherent\nlimitations, so use it sensibly!\n\n\n### Updating\n\nIn order to update document you can use high level methods\n`#update_attributes`, `#update_attribute` and `.update`. They run\nvalidation and callbacks.\n\n```ruby\nAddress.find(id).update_attributes(city: 'Chicago')\nAddress.find(id).update_attribute(:city, 'Chicago')\nAddress.update(id, city: 'Chicago')\n```\n\nThere are also some low level methods `#update`, `.update_fields` and\n`.upsert`. They don't run validation and callbacks (except `#update` -\nit runs `update` callbacks). All of them support conditional updates.\n`#upsert` will create new document if document with specified `id`\ndoesn't exist.\n\n```ruby\nAddress.find(id).update do |i|\n  i.set city: 'Chicago'\n  i.add latitude: 100\n  i.delete set_of_numbers: 10\nend\nAddress.find(id).update(if: { deliverable: true }) do |i|\n  i.set city: 'Chicago'\nend\nAddress.update_fields(id, city: 'Chicago')\nAddress.update_fields(id, { city: 'Chicago' }, if: { deliverable: true })\nAddress.upsert(id, city: 'Chicago')\nAddress.upsert(id, { city: 'Chicago' }, if: { deliverable: true })\n```\n\nBy default, `#upsert` will update all attributes of the document if it already exists.\nTo idempotently create-but-not-update a record, apply the `unless_exists` condition\nto its keys when you upsert.\n\n```ruby\nAddress.upsert(id, { city: 'Chicago' }, { unless_exists: [:id] })\n```\n\n### Deleting\n\nIn order to delete some items `delete_all` method should be used. Any\ncallback won't be called. Items delete in efficient way in batch.\n\n```ruby\nAddress.where(city: 'London').delete_all\n```\n\n### Global Secondary Indexes\n\nYou can define index with `global_secondary_index`:\n\n```ruby\nclass User\n  include Dynamoid::Document\n\n  field :name\n  field :age, :number\n\n  global_secondary_index hash_key: :age # Must come after field definitions.\nend\n```\n\nThere are the following options:\n* `hash_key` - is used as hash key of an index,\n* `range_key` - is used as range key of an index,\n* `projected_attributes` - list of fields to store in an index or has a\n  predefined value `:keys_only`, `:all`; `:keys_only` is a default,\n* `name` - an index will be created with this name when a table is\n  created; by default name is generated and contains table name and keys\n  names,\n* `read_capacity` - is used when table created and used as an index\n  capacity; by default equals `Dynamoid::Config.read_capacity`,\n* `write_capacity` - is used when table created and used as an index\n  capacity; by default equals `Dynamoid::Config.write_capacity`\n\nThe only mandatory option is `name`.\n\n**WARNING:** In order to use global secondary index in `Document.where`\nimplicitly you need to have all the attributes of the original table in\nthe index and declare it with option `projected_attributes: :all`:\n\n```ruby\nclass User\n  # ...\n\n  global_secondary_index hash_key: :age, projected_attributes: :all\nend\n```\n\nThere is only one implicit way to query Global and Local Secondary\nIndexes (GSI/LSI).\n\n#### Implicit\n\nThe second way implicitly uses your GSI through the `where` clauses and\ndeduces the index based on the query fields provided. Another added\nbenefit is that it is built into query chaining so you can use all the\nmethods used in normal querying. The explicit way from above would be\nrewritten as follows:\n\n```ruby\nwhere(dynamo_primary_key_column_name =\u003e dynamo_primary_key_value,\n      \"#{range_column}.#{range_modifier}\" =\u003e range_value)\n  .scan_index_forward(false)\n```\n\nThe only caveat with this method is that because it is also used for\ngeneral querying, it WILL NOT use a GSI unless it explicitly has defined\n`projected_attributes: :all` on the GSI in your model. This is because\nGSIs that do not have all attributes projected will only contain the\nindex keys and therefore will not return objects with fully resolved\nfield values. It currently opts to provide the complete results rather\nthan partial results unless you've explicitly looked up the data.\n\n*Future TODO could involve implementing `select` in chaining as well as\nresolving the fields with a second query against the table since a query\nagainst GSI then a query on base table is still likely faster than scan\non the base table*\n\n### Transactions in Dynamoid\n\n\u003e [!WARNING]\n\u003e Please note that this API is experimental and can be changed in\n\u003e future releases.\n\nMultiple modifying actions can be grouped together and submitted as an\nall-or-nothing operation. Atomic modifying operations are supported in\nDynamoid using transactions. If any action in the transaction fails they\nall fail.\n\nThe following actions are supported:\n\n* `#create`/`#create!` - add a new model if it does not already exist\n* `#save`/`#save!` - create or update model\n* `#update_attributes`/`#update_attributes!` - modifies one or more attributes from an existig\nmodel\n* `#delete` - remove an model without callbacks nor validations\n* `#destroy`/`#destroy!` - remove an model\n* `#upsert` - add a new model or update an existing one, no callbacks\n* `#update_fields` - update a model without its instantiation\n\nThese methods are supposed to behave exactly like their\nnon-transactional counterparts.\n\n\n#### Create models\n\nModels can be created inside of a transaction. The partition and sort\nkeys, if applicable, are used to determine uniqueness. Creating will\nfail with `Aws::DynamoDB::Errors::TransactionCanceledException` if a\nmodel already exists.\n\nThis example creates a user with a unique id and unique email address by\ncreating 2 models. An additional model is upserted in the same\ntransaction. Upsert will update `updated_at` but will not create\n`created_at`.\n\n```ruby\nuser_id = SecureRandom.uuid\nemail = 'bob@bob.bob'\n\nDynamoid::TransactionWrite.execute do |txn|\n  txn.create(User, id: user_id)\n  txn.create(UserEmail, id: \"UserEmail##{email}\", user_id: user_id)\n  txn.create(Address, id: 'A#2', street: '456')\n  txn.upsert(Address, 'A#1', street: '123')\nend\n```\n\n#### Save models\n\nModels can be saved in a transaction. New records are created otherwise\nthe model is updated. Save, create, update, validate and destroy\ncallbacks are called around the transaction as appropriate. Validation\nfailures will throw `Dynamoid::Errors::DocumentNotValid`.\n\n```ruby\nuser = User.find(1)\narticle = Article.new(body: 'New article text', user_id: user.id)\n\nDynamoid::TransactionWrite.execute do |txn|\n  txn.save(article)\n\n  user.last_article_id = article.id\n  txn.save(user)\nend\n```\n\n#### Update models\n\nA model can be updated by providing a model or primary key, and the fields to update.\n\n```ruby\nDynamoid::TransactionWrite.execute do |txn|\n  # change name and title for a user\n  txn.update_attributes(user, name: 'bob', title: 'mister')\n\n  # sets the name and title for a user\n  # The user is found by id (that equals 1)\n  txn.update_fields(User, '1', name: 'bob', title: 'mister')\n\n  # sets the name, increments a count and deletes a field\n  txn.update_fields(User, 1) do |t|\n    t.set(name: 'bob')\n    t.add(article_count: 1)\n    t.delete(:title)\n  end\n\n  # adds to a set of integers and deletes from a set of strings\n  txn.update_fields(User, 2) do |t|\n    t.add(friend_ids: [1, 2])\n    t.delete(child_names: ['bebe'])\n  end\nend\n```\n\n#### Destroy or delete models\n\nModels can be used or the model class and key can be specified.\n`#destroy` uses callbacks and validations. Use `#delete` to skip\ncallbacks and validations.\n\n```ruby\narticle = Article.find('1')\ntag = article.tag\n\nDynamoid::TransactionWrite.execute do |txn|\n  txn.destroy(article)\n  txn.delete(tag)\n\n  txn.delete(Tag, '2') # delete record with hash key '2' if it exists\n  txn.delete(Tag, 'key#abcd', 'range#1') # when sort key is required\nend\n```\n\n#### Validation failures that don't raise\n\nAll of the transaction methods can be called without the `!` which\nresults in `false` instead of a raised exception when validation fails.\nIgnoring validation failures can lead to confusion or bugs so always\ncheck return status when not using a method with `!`.\n\n```ruby\nuser = User.find('1')\nuser.red = true\n\nDynamoid::TransactionWrite.execute do |txn|\n  if txn.save(user) # won't raise validation exception\n    txn.update_fields(UserCount, user.id, count: 5)\n  else\n    puts 'ALERT: user not valid, skipping'\n  end\nend\n```\n\n#### Incrementally building a transaction\n\nTransactions can also be built without a block.\n\n```ruby\ntransaction = Dynamoid::TransactionWrite.new\n\ntransaction.create(User, id: user_id)\ntransaction.create(UserEmail, id: \"UserEmail##{email}\", user_id: user_id)\ntransaction.upsert(Address, 'A#1', street: '123')\n\ntransaction.commit # changes are persisted in this moment\n```\n\n### PartiQL\n\nTo run PartiQL statements `Dynamoid.adapter.execute` method should be\nused:\n\n```ruby\nDynamoid.adapter.execute(\"UPDATE users SET name = 'Mike' WHERE id = '1'\")\n```\n\nParameters are also supported:\n\n```ruby\nDynamoid.adapter.execute('SELECT * FROM users WHERE id = ?', ['1'])\n```\n\n## Configuration\n\nListed below are all configuration options.\n\n* `adapter` - useful only for the gem developers to switch to a new\n  adapter. Default and the only available value is `aws_sdk_v3`\n* `namespace` - prefix for table names, default is\n  `dynamoid_#{application_name}_#{environment}` for Rails application\n  and `dynamoid` otherwise\n* `logger` - by default it's a `Rails.logger` in Rails application and\n  `stdout` otherwise. You can disable logging by setting `nil` or\n  `false` values. Set `true` value to use defaults\n* `access_key` - DynamoDb custom access key for AWS credentials, override global\n  AWS credentials if they're present\n* `secret_key` - DynamoDb custom secret key for AWS credentials, override global\n  AWS credentials if they're present\n* `credentials` - DynamoDb custom pre-configured credentials, override global\n  AWS credentials if they're present\n* `region` - DynamoDb custom credentials for AWS, override global AWS\n  credentials if they're present\n* `batch_size` - when you try to load multiple items at once with\n* `batch_get_item` call Dynamoid loads them not with one api call but\n  piece by piece. Default is 100 items\n* `capacity_mode` - used at a table creation and means whether a table\n  read/write capacity mode will be on-demand or provisioned. Allowed\n  values are `:on_demand` and `:provisioned`. Default value is `nil` which\n  means provisioned mode will be used.\n* `read_capacity` - is used at table or indices creation. Default is 100\n  (units)\n* `write_capacity` - is used at table or indices creation. Default is 20\n  (units)\n* `warn_on_scan` - log warnings when scan table. Default is `true`\n* `endpoint` - if provided, it communicates with the DynamoDB listening\n  at the endpoint. This is useful for testing with\n  [DynamoDB Local](http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Tools.DynamoDBLocal.html)\n* `identity_map` - ensures that each object gets loaded only once by\n  keeping every loaded object in a map. Looks up objects using the map\n  when referring to them. Isn't thread safe. Default is `false`.\n  `Use Dynamoid::Middleware::IdentityMap` to clear identity map for each HTTP request\n* `timestamps` - by default Dynamoid sets `created_at` and `updated_at`\n  fields at model creation and updating. You can disable this\n  behavior by setting `false` value\n* `sync_retry_max_times` - when Dynamoid creates or deletes table\n  synchronously it checks for completion specified times. Default is 60\n  (times). It's a bit over 2 minutes by default\n* `sync_retry_wait_seconds` - time to wait between retries. Default is 2\n  (seconds)\n* `convert_big_decimal` - if `true` then Dynamoid converts numbers\n  stored in `Hash` in `raw` field to float. Default is `false`\n* `store_attribute_with_nil_value` - if `true` Dynamoid keeps attribute\n  with `nil` value in a document. Otherwise Dynamoid removes it while\n  saving a document. Default is `nil` which equals behaviour with `false`\n  value.\n* `models_dir` - `dynamoid:create_tables` rake task loads DynamoDb\n  models from this directory. Default is `./app/models`.\n* `application_timezone` - Dynamoid converts all `datetime` fields to\n  specified time zone when loads data from the storage.\n  Acceptable values - `:utc`, `:local` (to use system time zone) and\n  time zone name e.g. `Eastern Time (US \u0026 Canada)`. Default is `utc`\n* `dynamodb_timezone` - When a datetime field is stored in string format\n  Dynamoid converts it to specified time zone when saves a value to the\n  storage. Acceptable values - `:utc`, `:local` (to use system time\n  zone) and time zone name e.g. `Eastern Time (US \u0026 Canada)`. Default is\n  `utc`\n* `store_datetime_as_string` - if `true` then Dynamoid stores :datetime\n  fields in ISO 8601 string format. Default is `false`\n* `store_date_as_string` - if `true` then Dynamoid stores :date fields\n  in ISO 8601 string format. Default is `false`\n* `store_empty_string_as_nil` - store attribute's empty String value as NULL. Default is `true`\n* `store_boolean_as_native` - if `true` Dynamoid stores boolean fields\n  as native DynamoDB boolean values. Otherwise boolean fields are stored\n  as string values `'t'` and `'f'`. Default is `true`\n* `store_binary_as_native` - if `true` Dynamoid stores binary fields\n  as native DynamoDB binary values. Otherwise binary fields are stored\n  as Base64 encoded string values. Default is `false`\n* `backoff` - is a hash: key is a backoff strategy (symbol), value is\n  parameters for the strategy. Is used in batch operations. Default id\n  `nil`\n* `backoff_strategies`: is a hash and contains all available strategies.\n  Default is `{ constant: ..., exponential: ...}`\n* `log_formatter`: overrides default AWS SDK formatter. There are\n  several canned formatters: `Aws::Log::Formatter.default`,\n  `Aws::Log::Formatter.colored` and `Aws::Log::Formatter.short`. Please\n  look into `Aws::Log::Formatter` AWS SDK documentation in order to\n  provide own formatter.\n* `http_continue_timeout`: The number of seconds to wait for a\n  100-continue HTTP response before sending the request body. Default\n  option value is `nil`. If not specified effected value is `1`\n* `http_idle_timeout`: The number of seconds an HTTP connection is\n  allowed to sit idle before it is considered stale. Default option\n  value is `nil`. If not specified effected value is `5`\n* `http_open_timeout`: The number of seconds to wait when opening a HTTP\n  session. Default option value is `nil`. If not specified effected\n  value is `15`\n* `http_read_timeout`:The number of seconds to wait for HTTP response\n  data. Default option value is `nil`. If not specified effected value\n  is `60`\n* `create_table_on_save`: if `true` then Dynamoid creates a\n  corresponding table in DynamoDB at model persisting if the table\n  doesn't exist yet. Default is `true`\n\n\n## Concurrency\n\nDynamoid supports basic, ActiveRecord-like optimistic locking on save\noperations. Simply add a `lock_version` column to your table like so:\n\n```ruby\nclass MyTable\n  # ...\n\n  field :lock_version, :integer\n\n  # ...\nend\n```\n\nIn this example, all saves to `MyTable` will raise an\n`Dynamoid::Errors::StaleObjectError` if a concurrent process loaded,\nedited, and saved the same row. Your code should trap this exception,\nreload the row (so that it will pick up the newest values), and try the\nsave again.\n\nCalls to `update` and `update!` also increment the `lock_version`,\nhowever, they do not check the existing value. This guarantees that a\nupdate operation will raise an exception in a concurrent save operation,\nhowever a save operation will never cause an update to fail. Thus,\n`update` is useful \u0026 safe only for doing atomic operations (e.g.\nincrement a value, add/remove from a set, etc), but should not be used\nin a read-modify-write pattern.\n\n\n### Backoff strategies\n\n\nYou can use several methods that run efficiently in batch mode like\n`.find_all` and `.import`. It affects `Query` and `Scan` operations as\nwell.\n\nThe backoff strategy will be used when, for any reason, some items could\nnot be processed as part of a batch mode command. Operations will be\nre-run to process these items.\n\nExponential backoff is the recommended way to handle throughput limits\nexceeding and throttling on the table.\n\nThere are two built-in strategies - constant delay and truncated binary\nexponential backoff. By default no backoff is used but you can specify\none of the built-in ones:\n\n```ruby\nDynamoid.configure do |config|\n  config.backoff = { constant: 2.second }\nend\n\nDynamoid.configure do |config|\n  config.backoff = { exponential: { base_backoff: 0.2.seconds, ceiling: 10 } }\nend\n\n```\n\nYou can just specify strategy without any arguments to use default\npresets:\n\n```ruby\nDynamoid.configure do |config|\n  config.backoff = :constant\nend\n```\n\nYou can use your own strategy in the following way:\n\n```ruby\nDynamoid.configure do |config|\n  config.backoff_strategies[:custom] = lambda do |n|\n    -\u003e { sleep rand(n) }\n  end\n\n  config.backoff = { custom: 10 }\nend\n```\n\n\n## Rake Tasks\n\nThere are a few Rake tasks available out of the box:\n\n* `rake dynamoid:create_tables`\n* `rake dynamoid:ping`\n\nIn order to use them in non-Rails application they should be required\nexplicitly:\n\n```ruby\n# Rakefile\n\nRake::Task.define_task(:environment)\nrequire 'dynamoid/tasks'\n```\n\nThe Rake tasks depend on `:environment` task so it should be declared as\nwell.\n\n## Test Environment\n\nIn test environment you will most likely want to clean the database\nbetween test runs to keep tests completely isolated. This can be\nachieved like so\n\n```ruby\nmodule DynamoidReset\n  def self.all\n    Dynamoid.adapter.list_tables.each do |table|\n      # Only delete tables in our namespace\n      if table =~ /^#{Dynamoid::Config.namespace}/\n        Dynamoid.adapter.delete_table(table)\n      end\n    end\n    Dynamoid.adapter.tables.clear\n    # Recreate all tables to avoid unexpected errors\n    Dynamoid.included_models.each { |m| m.create_table(sync: true) }\n  end\nend\n\n# Reduce noise in test output\nDynamoid.logger.level = Logger::FATAL\n```\n\nIf you're using RSpec you can invoke the above like so:\n\n```ruby\nRSpec.configure do |config|\n  config.before(:each) do\n    DynamoidReset.all\n  end\nend\n```\n\nIn addition, the first test for each model may fail if the relevant models are not included in `included_models`. This can be fixed by adding this line before the `DynamoidReset` module:\n```ruby\nDir[File.join(Dynamoid::Config.models_dir, '**/*.rb')].sort.each { |file| require file }\n```\nNote that this will require _all_ models in your models folder - you can also explicitly require only certain models if you would prefer to.\n\nIn Rails, you may also want to ensure you do not delete non-test data\naccidentally by adding the following to your test environment setup:\n\n```ruby\nraise \"Tests should be run in 'test' environment only\" if Rails.env != 'test'\n\nDynamoid.configure do |config|\n  config.namespace = \"#{Rails.application.railtie_name}_#{Rails.env}\"\nend\n```\n\n## Logging\n\nThere is a config option `logger`. Dynamoid writes requests and\nresponses to DynamoDB using this logger on the `debug` level. So in\norder to troubleshoot and debug issues just set it:\n\n```ruby\nclass User\n  include Dynamoid::Document\n  field name\nend\n\nDynamoid.config.logger.level = :debug\nDynamoid.config.endpoint = 'http://localhost:8000'\n\nUser.create(name: 'Alex')\n\n# =\u003e D, [2019-05-12T20:01:07.840051 #75059] DEBUG -- : put_item | Request \"{\\\"TableName\\\":\\\"dynamoid_users\\\",\\\"Item\\\":{\\\"created_at\\\":{\\\"N\\\":\\\"1557680467.608749\\\"},\\\"updated_at\\\":{\\\"N\\\":\\\"1557680467.608809\\\"},\\\"id\\\":{\\\"S\\\":\\\"1227eea7-2c96-4b8a-90d9-77b38eb85cd0\\\"}},\\\"Expected\\\":{\\\"id\\\":{\\\"Exists\\\":false}}}\" | Response \"{}\"\n\n# =\u003e D, [2019-05-12T20:01:07.842397 #75059] DEBUG -- : (231.28 ms) PUT ITEM - [\"dynamoid_users\", {:created_at=\u003e0.1557680467608749e10, :updated_at=\u003e0.1557680467608809e10, :id=\u003e\"1227eea7-2c96-4b8a-90d9-77b38eb85cd0\", :User=\u003enil}, {}]\n```\n\nThe first line is a body of HTTP request and response. The second line -\nDynamoid internal logging of API call (`PUT ITEM` in our case) with\ntiming (231.28 ms).\n\n## Credits\n\nDynamoid borrows code, structure, and even its name very liberally from\nthe truly amazing [Mongoid](https://github.com/mongoid/mongoid). Without\nMongoid to crib from none of this would have been possible, and I hope\nthey don't mind me reusing their very awesome ideas to make DynamoDB\njust as accessible to the Ruby world as MongoDB.\n\nAlso, without contributors the project wouldn't be nearly as awesome. So\nmany thanks to:\n\n* [Chris Hobbs](https://github.com/ckhsponge)\n* [Logan Bowers](https://github.com/loganb)\n* [Lane LaRue](https://github.com/luxx)\n* [Craig Heneveld](https://github.com/cheneveld)\n* [Anantha Kumaran](https://github.com/ananthakumaran)\n* [Jason Dew](https://github.com/jasondew)\n* [Luis Arias](https://github.com/luisantonioa)\n* [Stefan Neculai](https://github.com/stefanneculai)\n* [Philip White](https://github.com/philipmw) *\n* [Peeyush Kumar](https://github.com/peeyush1234)\n* [Sumanth Ravipati](https://github.com/sumocoder)\n* [Pascal Corpet](https://github.com/pcorpet)\n* [Brian Glusman](https://github.com/bglusman) *\n* [Peter Boling](https://github.com/pboling) *\n* [Andrew Konchin](https://github.com/andrykonchin) *\n\n\\* Current Maintainers\n\n## Running the tests\n\nRunning the tests is fairly simple. You should have an instance of\nDynamoDB running locally. Follow these steps to setup your test\nenvironment.\n\n * First download and unpack the latest version of DynamoDB. We have a\n   script that will do this for you if you use bash, and homebrew on a Mac.\n\n    ```shell\n    bin/setup\n    ```\n\n * Start the local instance of DynamoDB to listen in ***8000*** port\n\n    ```shell\n    bin/start_dynamodblocal\n    ```\n\n * and lastly, use `rake` to run the tests.\n\n    ```shell\n    rake\n    ```\n\n * When you are done, remember to stop the local test instance of\n   dynamodb\n\n    ```shell\n    bin/stop_dynamodblocal\n    ```\n\nIf you run into issues, please try these steps first.\nNOTE: You can use any version manager: rvm, rbenv, chruby, asdf-ruby\n```shell\nasdf install ruby 3.1.1\nasdf local ruby 3.1.1\ngem update --system\nbundle install\n```\n\n## Security\n\nSee [SECURITY.md][security].\n\n## Related links\n\nThis documentation may be useful for the contributors:\n- \u003chttps://docs.aws.amazon.com/sdk-for-ruby/v3/developer-guide/welcome.html\u003e\n- \u003chttps://docs.aws.amazon.com/sdk-for-ruby/v3/api/index.html\u003e\n\n## License\n\nThe gem is available as open source under the terms of\nthe [MIT License][license] [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)][license-ref].\nSee [LICENSE][license] for the official [Copyright Notice][copyright-notice-explainer].\n\n[copyright-notice-explainer]: https://opensource.stackexchange.com/questions/5778/why-do-licenses-such-as-the-mit-license-specify-a-single-year\n\n[license]: https://github.com/Dynamoid/dynamoid/blob/master/LICENSE.txt\n\n[license-ref]: https://opensource.org/licenses/MIT\n\n[security]: https://github.com/Dynamoid/dynamoid/blob/master/SECURITY.md\n\n[⛳️gem]: https://rubygems.org/gems/dynamoid\n[⛳️version-img]: http://img.shields.io/gem/v/dynamoid.svg\n[⛳cclim-maint]: https://codeclimate.com/github/Dynamoid/dynamoid/maintainability\n[⛳cclim-maint-img♻️]: https://api.codeclimate.com/v1/badges/27fd8b6b7ff338fa4914/maintainability\n[🏘coveralls]: https://coveralls.io/github/Dynamoid/dynamoid?branch=master\n[🏘coveralls-img]: https://coveralls.io/repos/github/Dynamoid/dynamoid/badge.svg?branch=master\n[🖇codecov]: https://codecov.io/gh/Dynamoid/dynamoid\n[🖇codecov-img♻️]: https://codecov.io/gh/Dynamoid/dynamoid/branch/master/graph/badge.svg?token=84WeeoxaN9\n[🖇src-license]: https://github.com/Dynamoid/dynamoid/blob/master/LICENSE.txt\n[🖇src-license-img]: https://img.shields.io/badge/License-MIT-green.svg\n[🖐gitmoji]: https://gitmoji.dev\n[🖐gitmoji-img]: https://img.shields.io/badge/gitmoji-3.9.0-FFDD67.svg?style=flat\n[🚎yard]: https://www.rubydoc.info/gems/dynamoid\n[🚎yard-img]: https://img.shields.io/badge/yard-docs-blue.svg?style=flat\n[🧮semver]: http://semver.org/\n[🧮semver-img]: https://img.shields.io/badge/semver-2.0.0-FFDD67.svg?style=flat\n[🖐contributors]: https://github.com/Dynamoid/dynamoid/graphs/contributors\n[🖐contributors-img]: https://img.shields.io/github/contributors-anon/Dynamoid/dynamoid\n[📗keep-changelog]: https://keepachangelog.com/en/1.0.0/\n[📗keep-changelog-img]: https://img.shields.io/badge/keep--a--changelog-1.0.0-FFDD67.svg?style=flat\n[🖇sponsor-img]: https://img.shields.io/opencollective/all/dynamoid\n[🖇sponsor]: https://opencollective.com/dynamoid\n[🖇triage-help]: https://www.codetriage.com/dynamoid/dynamoid\n[🖇triage-help-img]: https://www.codetriage.com/dynamoid/dynamoid/badges/users.svg\n[🏘sup-wf]: https://github.com/Dynamoid/dynamoid/actions/workflows/ci.yml?query=branch%3Amaster\n[🏘sup-wf-img]: https://github.com/Dynamoid/dynamoid/actions/workflows/ci.yml/badge.svg?branch=master\n","funding_links":["https://github.com/sponsors/Dynamoid","https://github.com/sponsors/pboling","https://opencollective.com/dynamoid","https://tidelift.com/funding/github/rubygems/dynamoid"],"categories":["Ruby"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FDynamoid%2Fdynamoid","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FDynamoid%2Fdynamoid","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FDynamoid%2Fdynamoid/lists"}