{"id":15288790,"url":"https://github.com/agrimatics/activemodel-datastore","last_synced_at":"2025-05-07T15:23:36.304Z","repository":{"id":46234230,"uuid":"55617068","full_name":"Agrimatics/activemodel-datastore","owner":"Agrimatics","description":"Ruby on Rails with Active Model and Google Cloud Datastore. Extracted from Agrimatics Aero.","archived":false,"fork":false,"pushed_at":"2024-03-05T16:30:14.000Z","size":397,"stargazers_count":51,"open_issues_count":3,"forks_count":17,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-04-19T16:25:52.219Z","etag":null,"topics":["activemodel","google-cloud","google-cloud-datastore","rails"],"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/Agrimatics.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"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}},"created_at":"2016-04-06T15:19:50.000Z","updated_at":"2025-03-05T14:54:14.000Z","dependencies_parsed_at":"2025-01-02T09:11:25.552Z","dependency_job_id":"fe96c412-300a-4ad1-8221-841602107c8e","html_url":"https://github.com/Agrimatics/activemodel-datastore","commit_stats":{"total_commits":110,"total_committers":5,"mean_commits":22.0,"dds":"0.21818181818181814","last_synced_commit":"6d5d0c0abcd9fa7518d82551f44e39e2abf940f5"},"previous_names":[],"tags_count":13,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Agrimatics%2Factivemodel-datastore","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Agrimatics%2Factivemodel-datastore/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Agrimatics%2Factivemodel-datastore/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Agrimatics%2Factivemodel-datastore/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Agrimatics","download_url":"https://codeload.github.com/Agrimatics/activemodel-datastore/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252903350,"owners_count":21822428,"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":["activemodel","google-cloud","google-cloud-datastore","rails"],"created_at":"2024-09-30T15:53:11.263Z","updated_at":"2025-05-07T15:23:36.283Z","avatar_url":"https://github.com/Agrimatics.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"Active Model Datastore\n===================================\n\nMakes the [google-cloud-datastore](https://github.com/GoogleCloudPlatform/google-cloud-ruby/tree/master/google-cloud-datastore) gem compliant with [active_model](https://github.com/rails/rails/tree/master/activemodel) conventions and compatible with your Rails 5+ applications. \n---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n\nWhy would you want to use Google's NoSQL [Cloud Datastore](https://cloud.google.com/datastore) \nwith Rails? \n\nWhen you want a Rails app backed by a managed, massively-scalable datastore solution. Cloud Datastore \nautomatically handles sharding and replication. It is a highly available and durable database that \nautomatically scales to handle your applications' load. Cloud Datastore is a schemaless database \nsuited for unstructured or semi-structured application data.\n\n[![Gem Version](https://badge.fury.io/rb/activemodel-datastore.svg)](https://badge.fury.io/rb/activemodel-datastore)\n[![Build Status](https://travis-ci.org/Agrimatics/activemodel-datastore.svg?branch=master)](https://travis-ci.org/Agrimatics/activemodel-datastore)\n \n## Table of contents\n \n- [Setup](#setup)\n- [Model Example](#model)\n- [Controller Example](#controller)\n- [Retrieving Entities](#queries)\n- [Datastore Consistency](#consistency)\n- [Datastore Indexes](#indexes)\n- [Datastore Emulator](#emulator)\n- [Example Rails App](#rails)\n- [CarrierWave File Uploads](#carrierwave)\n- [Track Changes](#track_changes)\n- [Nested Forms](#nested)\n- [Datastore Gotchas](#gotchas)\n \n## \u003ca name=\"setup\"\u003e\u003c/a\u003eSetup\n \nGenerate your Rails app without ActiveRecord:\n \n```bash\nrails new my_app -O\n```\n\nYou can remove the db/ directory as it won't be needed.\n\nTo install, add this line to your `Gemfile` and run `bundle install`:\n \n```ruby\ngem 'activemodel-datastore'\n```\n  \nCreate a Google Cloud account [here](https://cloud.google.com) and create a project.\n\nGoogle Cloud requires the Project ID and Service Account Credentials to connect to the Datastore API.\n \n*Follow the [activation instructions](https://cloud.google.com/datastore/docs/activate) to enable the \nGoogle Cloud Datastore API. When running on Google Cloud Platform environments the Service Account \ncredentials will be discovered automatically. When running on other environments (such as AWS or Heroku)\nyou need to create a service account with the role of editor and generate json credentials.*\n\nSet your project id in an `ENV` variable named `GCLOUD_PROJECT`.\n\nTo locate your project ID:\n\n1. Go to the Cloud Platform Console.\n2. From the projects list, select the name of your project.\n3. On the left, click Dashboard. The project name and ID are displayed in the Dashboard.\n\nIf you have an external application running on a platform outside of Google Cloud you also need to \nprovide the Service Account credentials. They are specified in two additional `ENV` variables named \n`SERVICE_ACCOUNT_CLIENT_EMAIL` and `SERVICE_ACCOUNT_PRIVATE_KEY`. The values for these two `ENV` \nvariables will be in the downloaded service account json credentials file.\n\n```bash\nSERVICE_ACCOUNT_PRIVATE_KEY = -----BEGIN PRIVATE KEY-----\\nMIIFfb3...5dmFtABy\\n-----END PRIVATE KEY-----\\n\nSERVICE_ACCOUNT_CLIENT_EMAIL = web-app@app-name.iam.gserviceaccount.com\n```\n\nOn Heroku the `ENV` variables can be set under 'Settings' -\u003e 'Config Variables'.\n\nActive Model Datastore will then handle the authentication for you, and the datastore instance can \nbe accessed with `CloudDatastore.dataset`.\n\nThere is an example Puma config file [here](https://github.com/Agrimatics/activemodel-datastore/blob/master/test/support/datastore_example_rails_app/config/puma.rb).\n \n## \u003ca name=\"model\"\u003e\u003c/a\u003eModel Example\n \nLet's start by implementing the model:\n\n```ruby\nclass User\n  include ActiveModel::Datastore\n\n  attr_accessor :email, :enabled, :name, :role, :state\n\n  def entity_properties\n    %w[email enabled name role]\n  end\nend\n```\n\nData objects in Cloud Datastore are known as entities. Entities are of a kind. An entity has one \nor more named properties, each of which can have one or more values. Think of them like this:\n* 'Kind' (which is your table and the name of your Rails model)\n* 'Entity' (which is the record from the table)\n* 'Property' (which is the attribute of the record)\n\nThe `entity_properties` method defines an Array of properties that belong to the entity in cloud \ndatastore. Define the attributes of your model using `attr_accessor`. With this approach, Rails \ndeals solely with ActiveModel objects. The objects are converted to/from entities automatically \nduring save/query operations. You can still use virtual attributes on the model (such as the \n`:state` attribute above) by simply excluding it from `entity_properties`. In this example state \nis available to the model but won't be persisted with the entity in datastore.\n\nValidations work as you would expect:\n\n```ruby\nclass User\n  include ActiveModel::Datastore\n\n  attr_accessor :email, :enabled, :name, :role, :state\n\n  validates :email, format: { with: /\\A([^@\\s]+)@((?:[-a-z0-9]+\\.)+[a-z]{2,})\\z/i }\n  validates :name, presence: true, length: { maximum: 30 }\n\n  def entity_properties\n    %w[email enabled name role]\n  end\nend\n```\n\nCallbacks work as you would expect. We have also added the ability to set default values through \n[`default_property_value`](http://www.rubydoc.info/gems/activemodel-datastore/ActiveModel/Datastore/PropertyValues#default_property_value-instance_method) \nand type cast the format of values through [`format_property_value`](http://www.rubydoc.info/gems/activemodel-datastore/ActiveModel/Datastore/PropertyValues#format_property_value-instance_method):\n\n```ruby\nclass User\n  include ActiveModel::Datastore\n\n  attr_accessor :email, :enabled, :name, :role, :state\n\n  before_validation :set_default_values\n  after_validation :format_values\n  \n  before_save { puts '** something can happen before save **'}\n  after_save { puts '** something can happen after save **'}\n\n  validates :email, format: { with: /\\A([^@\\s]+)@((?:[-a-z0-9]+\\.)+[a-z]{2,})\\z/i }\n  validates :name, presence: true, length: { maximum: 30 }\n  validates :role, presence: true\n\n  def entity_properties\n    %w[email enabled name role]\n  end\n\n  def set_default_values\n    default_property_value :enabled, true\n    default_property_value :role, 1\n  end\n\n  def format_values\n    format_property_value :role, :integer\n  end\nend\n```\n\n## \u003ca name=\"controller\"\u003e\u003c/a\u003eController Example\n\nNow on to the controller! A scaffold generated controller works out of the box:\n\n```ruby\nclass UsersController \u003c ApplicationController\n  before_action :set_user, only: [:show, :edit, :update, :destroy]\n\n  def index\n    @users = User.all\n  end\n\n  def show\n  end\n\n  def new\n    @user = User.new\n  end\n\n  def edit\n  end\n\n  def create\n    @user = User.new(user_params)\n    respond_to do |format|\n      if @user.save\n        format.html { redirect_to @user, notice: 'User was successfully created.' }\n      else\n        format.html { render :new }\n      end\n    end\n  end\n\n  def update\n    respond_to do |format|\n      if @user.update(user_params)\n        format.html { redirect_to @user, notice: 'User was successfully updated.' }\n      else\n        format.html { render :edit }\n      end\n    end\n  end\n\n  def destroy\n    @user.destroy\n    respond_to do |format|\n      format.html { redirect_to users_url, notice: 'User was successfully destroyed.' }\n    end\n  end\n\n  private\n\n  def set_user\n    @user = User.find(params[:id])\n  end\n\n  def user_params\n    params.require(:user).permit(:email, :name)\n  end\nend\n```\n\n## \u003ca name=\"queries\"\u003e\u003c/a\u003eRetrieving Entities\n\nEach entity in Cloud Datastore has a key that uniquely identifies it. The key consists of the \nfollowing components:\n\n* the kind of the entity, which is User in these examples\n* an identifier for the individual entity, which can be either a a key name string or an integer numeric ID\n* an optional ancestor path locating the entity within the Cloud Datastore hierarchy\n\n#### [all(options = {})](http://www.rubydoc.info/gems/activemodel-datastore/ActiveModel%2FDatastore%2FClassMethods:all)\nQueries entities using the provided options. When a limit option is provided queries up to the limit \nand returns results with a cursor.\n```ruby\nusers = User.all(options = {})\n\nparent_key = CloudDatastore.dataset.key('Parent', 12345)\nusers = User.all(ancestor: parent_key)\n\nusers = User.all(ancestor: parent_key, where: ['name', '=', 'Bryce'])\n\nusers = User.all(where: [['name', '=', 'Ian'], ['enabled', '=', true]])\n\nusers, cursor = User.all(limit: 7)\n\n# @param [Hash] options The options to construct the query with.\n#\n# @option options [Google::Cloud::Datastore::Key] :ancestor Filter for inherited results.\n# @option options [String] :cursor Sets the cursor to start the results at.\n# @option options [Integer] :limit Sets a limit to the number of results to be returned.\n# @option options [String] :order Sort the results by property name.\n# @option options [String] :desc_order Sort the results by descending property name.\n# @option options [Array] :select Retrieve only select properties from the matched entities.\n# @option options [Array] :distinct_on Group results by a list of properties.\n# @option options [Array] :where Adds a property filter of arrays in the format[name, operator, value].\n```\n\n#### [find(*ids, parent: nil)](http://www.rubydoc.info/gems/activemodel-datastore/ActiveModel%2FDatastore%2FClassMethods:find)\nFind entity by id - this can either be a specific id (1), a list of ids (1, 5, 6), or an array of ids ([5, 6, 10]). \nThe parent key is optional. This method is a lookup by key and results will be strongly consistent.\n```ruby\nuser = User.find(1)\n\nparent_key = CloudDatastore.dataset.key('Parent', 12345)\nuser = User.find(1, parent: parent_key)\n\nusers = User.find(1, 2, 3)\n```\n\n#### [find_by(args)](http://www.rubydoc.info/gems/activemodel-datastore/ActiveModel%2FDatastore%2FClassMethods:find_by)\nQueries for the first entity matching the specified condition.\n```ruby\nuser = User.find_by(name: 'Joe')\n\nuser = User.find_by(name: 'Bryce', ancestor: parent_key)\n```\n\nCloud Datastore has documentation on how [Datastore Queries](https://cloud.google.com/datastore/docs/concepts/queries#datastore-basic-query-ruby) \nwork, and pay special attention to the the [restrictions](https://cloud.google.com/datastore/docs/concepts/queries#restrictions_on_queries).\n\n## \u003ca name=\"consistency\"\u003e\u003c/a\u003eDatastore Consistency\n\nCloud Datastore is a non-relational databases, or NoSQL database. It distributes data over many \nmachines and uses synchronous replication over a wide geographic area. Because of this architecture \nit offers a balance of strong and eventual consistency.\n\nWhat is eventual consistency?\n\nIt means that an updated entity value may not be immediately visible when executing a query. \nEventual consistency is a theoretical guarantee that, provided no new updates to an entity are made, \nall reads of the entity will eventually return the last updated value.\n\nIn the context of a Rails app, there are times that eventual consistency is not ideal. For example,\nlet's say you create a user entity with a key that looks like this:\n\n`@key=#\u003cGoogle::Cloud::Datastore::Key @kind=\"User\", @id=1\u003e`\n\nand then immediately redirect to the index view of users. There is a good chance that your new user \nis not yet visible in the list. If you perform a refresh on the index view a second or two later \nthe user will appear.\n\n\"Wait a minute!\" you say. \"This is crap!\" you say. Fear not! We can make the query of users strongly\nconsistent. We just need to use entity groups and ancestor queries. An entity group is a hierarchy \nformed by a root entity and its children. To create an entity group, you specify an ancestor path \nfor the entity which is a parent key as part of the child key.\n\nBefore using the `save` method, assign the `parent_key_id` attribute an ID. Let's say that 12345 \nrepresents the ID of the company that the users belong to. The key of the user entity will now \nlook like this:\n\n`@key=#\u003cGoogle::Cloud::Datastore::Key @kind=\"User\", @id=1, @parent=#\u003cGoogle::Cloud::Datastore::Key @kind=\"ParentUser\", @id=12345\u003e\u003e`\n\nAll of the User entities will now belong to an entity group named ParentUser and can be queried by the \nCompany ID. When we query for the users we will provide User.parent_key(12345) as the ancestor option.\n \n*Ancestor queries are always strongly consistent.*\n\nHowever, there is a small downside. Entities with the same ancestor are limited to 1 write per second.\nAlso, the entity group relationship cannot be changed after creating the entity (as you can't modify \nan entity's key after it has been saved).\n\nThe Users controller would now look like this:\n\n```ruby\nclass UsersController \u003c ApplicationController\n  before_action :set_user, only: [:show, :edit, :update, :destroy]\n\n  def index\n    @users = User.all(ancestor: User.parent_key(12345))\n  end\n\n  def show\n  end\n\n  def new\n    @user = User.new\n  end\n\n  def edit\n  end\n\n  def create\n    @user = User.new(user_params)\n    @user.parent_key_id = 12345\n    respond_to do |format|\n      if @user.save\n        format.html { redirect_to @user, notice: 'User was successfully created.' }\n      else\n        format.html { render :new }\n      end\n    end\n  end\n\n  def update\n    respond_to do |format|\n      if @user.update(user_params)\n        format.html { redirect_to @user, notice: 'User was successfully updated.' }\n      else\n        format.html { render :edit }\n      end\n    end\n  end\n\n  def destroy\n    @user.destroy\n    respond_to do |format|\n      format.html { redirect_to users_url, notice: 'User was successfully destroyed.' }\n    end\n  end\n\n  private\n\n  def set_user\n    @user = User.find(params[:id], parent: User.parent_key(12345))\n  end\n\n  def user_params\n    params.require(:user).permit(:email, :name)\n  end\nend\n```\n\nSee here for the Cloud Datastore documentation on [Data Consistency](https://cloud.google.com/datastore/docs/concepts/structuring_for_strong_consistency).\n\n## \u003ca name=\"indexes\"\u003e\u003c/a\u003eDatastore Indexes\n\nEvery cloud datastore query requires an index. Yes, you read that correctly. Every single one. The \nindexes contain entity keys in a sequence specified by the index's properties and, optionally, \nthe entity's ancestors.\n\nThere are two types of indexes, *built-in* and *composite*.\n\n#### Built-in\nBy default, Cloud Datastore automatically predefines an index for each property of each entity kind. \nThese single property indexes are suitable for simple types of queries. These indexes are free and\ndo not count against your index limit.\n\n#### Composite\nComposite index multiple property values per indexed entity. Composite indexes support complex \nqueries and are defined in an index.yaml file.\n\nComposite indexes are required for queries of the following form:\n\n* queries with ancestor and inequality filters\n* queries with one or more inequality filters on a property and one or more equality filters on other properties\n* queries with a sort order on keys in descending order\n* queries with multiple sort orders\n* queries with one or more filters and one or more sort orders\n\n*NOTE*: Inequality filters are LESS_THAN, LESS_THAN_OR_EQUAL, GREATER_THAN, GREATER_THAN_OR_EQUAL.\n\nGoogle has excellent doc regarding datastore indexes [here](https://cloud.google.com/datastore/docs/concepts/indexes).\n\nThe datastore emulator generates composite indexes in an index.yaml file automatically. The file\ncan be found in /tmp/local_datastore/WEB-INF/index.yaml. If your localhost Rails app exercises every \npossible query the application will issue, using every combination of filter and sort order, the \ngenerated entries will represent your complete set of indexes.\n\nOne thing to note is that the datastore emulator caches indexes. As you add and modify application \ncode you might find that the local datastore index.yaml contains indexes that are no longer needed. \nIn this scenario try deleting the index.yaml and restarting the emulator. Navigate through your Rails\napp and the index.yaml will be built from scratch.\n\n## \u003ca name=\"emulator\"\u003e\u003c/a\u003eDatastore Emulator\n\nInstall the Google Cloud SDK.\n\n    $ curl https://sdk.cloud.google.com | bash\n    \nYou can check the version of the SDK and the components installed with:\n\n    $ gcloud components list\n    \nInstall the Cloud Datastore Emulator, which provides local emulation of the production Cloud \nDatastore environment and the gRPC API. However, you'll need to do a small amount of configuration \nbefore running the application against the emulator, see [here.](https://cloud.google.com/datastore/docs/tools/datastore-emulator)\n    \n    $ gcloud components install cloud-datastore-emulator \n    \nAdd the following line to your ~/.bash_profile:\n        \n    export PATH=\"~/google-cloud-sdk/platform/cloud-datastore-emulator:$PATH\"\n        \nRestart your shell:\n        \n    exec -l $SHELL    \n\nTo create the local development datastore execute the following from the root of the project:\n\n    $ cloud_datastore_emulator create tmp/local_datastore\n    \nTo create the local test datastore execute the following from the root of the project:\n    \n    $ cloud_datastore_emulator create tmp/test_datastore\n\nTo start the local Cloud Datastore emulator:\n\n    $ cloud_datastore_emulator start --port=8180 tmp/local_datastore\n    \n## \u003ca name=\"rails\"\u003e\u003c/a\u003eExample Rails App\n\nThere is an example Rails 5 app in the test directory [here](https://github.com/Agrimatics/activemodel-datastore/tree/master/test/support/datastore_example_rails_app).\n\n ```bash\n $ bundle\n $ cloud_datastore_emulator create tmp/local_datastore\n $ cloud_datastore_emulator create tmp/test_datastore\n $ ./start-local-datastore.sh\n $ rails s\n ```\n \n Navigate to http://localhost:3000.\n \n## \u003ca name=\"carrierwave\"\u003e\u003c/a\u003eCarrierWave File Uploads\n\nActive Model Datastore has built in support for [CarrierWave](https://github.com/carrierwaveuploader/carrierwave) \nwhich is a simple and extremely flexible way to upload files from Rails applications. You can use \ndifferent stores, including filesystem and cloud storage such as Google Cloud Storage or AWS.\n\nSimply require `active_model/datastore/carrier_wave_uploader` and extend your model with the \nCarrierWaveUploader (after including ActiveModel::Datastore). Follow the CarrierWave \n[instructions](https://github.com/carrierwaveuploader/carrierwave#getting-started) for generating \nan uploader.\n\nIn this example it will be something like:\n\n`rails generate uploader ProfileImage`\n\nDefine an attribute on the model for your file(s). You can then mount the uploaders using \n`mount_uploader` (single file) or `mount_uploaders` (array of files). Don't forget to add the new\nattribute to `entity_properties` and whitelist the attribute in the controller if using strong \nparameters.\n\n```ruby\nrequire 'active_model/datastore/carrier_wave_uploader'\n\nclass User\n  include ActiveModel::Datastore\n  extend CarrierWaveUploader\n\n  attr_accessor :email, :enabled, :name, :profile_image, :role\n  \n  mount_uploader :profile_image, ProfileImageUploader\n\n  def entity_properties\n    %w[email enabled name profile_image role]\n  end\nend\n```\n\nYou will want to add something like this to your Rails form:\n\n`\u003c%= form.file_field :profile_image %\u003e`\n\n## \u003ca name=\"track_changes\"\u003e\u003c/a\u003eTrack Changes\n\nTODO: document the change tracking implementation.\n\n## \u003ca name=\"nested\"\u003e\u003c/a\u003eNested Forms\n\nAdds support for nested attributes to ActiveModel. Heavily inspired by \nRails ActiveRecord::NestedAttributes.\n\nNested attributes allow you to save attributes on associated records along with the parent.\nIt's used in conjunction with fields_for to build the nested form elements.\n\nSee Rails [ActionView::Helpers::FormHelper::fields_for](http://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html#method-i-fields_for) for more info.\n\n*NOTE*: Unlike ActiveRecord, the way that the relationship is modeled between the parent and\nchild is not enforced. With NoSQL the relationship could be defined by any attribute, or with\ndenormalization exist within the same entity. This library provides a way for the objects to\nbe associated yet saved to the datastore in any way that you choose.\n\nYou enable nested attributes by defining an `:attr_accessor` on the parent with the pluralized \nname of the child model.\n\nNesting also requires that a `\u003cassociation_name\u003e_attributes=` writer method is defined in your\nparent model. If an object with an association is instantiated with a params hash, and that\nhash has a key for the association, Rails will call the `\u003cassociation_name\u003e_attributes=`\nmethod on that object. Within the writer method call `assign_nested_attributes`, passing in\nthe association name and attributes.\n\nLet's say we have a parent Recipe with Ingredient children.\n\nStart by defining within the Recipe model:\n* an attr_accessor of `:ingredients`\n* a writer method named `ingredients_attributes=`\n* the `validates_associated` method can be used to validate the nested objects\n\nExample:\n\n```ruby\nclass Recipe\n  attr_accessor :ingredients\n  validates :ingredients, presence: true\n  validates_associated :ingredients\n\n  def ingredients_attributes=(attributes)\n    assign_nested_attributes(:ingredients, attributes)\n  end\nend\n```\n\nYou may also set a `:reject_if` proc to silently ignore any new record hashes if they fail to\npass your criteria. For example:\n\n```ruby\nclass Recipe\n def ingredients_attributes=(attributes)\n   reject_proc = proc { |attributes| attributes['name'].blank? }\n   assign_nested_attributes(:ingredients, attributes, reject_if: reject_proc)\n end\nend\n```\n\n Alternatively,`:reject_if` also accepts a symbol for using methods:\n\n```ruby\nclass Recipe\n  def ingredients_attributes=(attributes)\n    reject_proc = proc { |attributes| attributes['name'].blank? }\n    assign_nested_attributes(:ingredients, attributes, reject_if: reject_recipes)\n  end\n\n  def reject_recipes(attributes)\n    attributes['name'].blank?\n  end\nend\n```\n\nWithin the parent model `valid?` will validate the parent and associated children and\n`nested_models` will return the child objects. If the nested form submitted params contained\na truthy `_destroy` key, the appropriate nested_models will have `marked_for_destruction` set\nto True.\n\n## \u003ca name=\"gotchas\"\u003e\u003c/a\u003eDatastore Gotchas\n#### Ordering of query results is undefined when no sort order is specified.\nWhen a query does not specify a sort order, the results are returned in the order they are retrieved. \nAs Cloud Datastore implementation evolves (or if a project's indexes change), this order may change. \nTherefore, if your application requires its query results in a particular order, be sure to specify \nthat sort order explicitly in the query.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fagrimatics%2Factivemodel-datastore","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fagrimatics%2Factivemodel-datastore","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fagrimatics%2Factivemodel-datastore/lists"}