module ActiveModel::Datastore

Active Model Datastore

Makes the google-cloud-datastore gem compliant with active_model conventions and compatible with your Rails 5+ applications.

Let's start by implementing the model:

class User
  include ActiveModel::Datastore

  attr_accessor :email, :enabled, :name, :role, :state

  before_validation :set_default_values
  after_validation :format_values

  before_save { puts '** something can happen before save **'}
  after_save { puts '** something can happen after save **'}

  validates :email, format: { with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i }
  validates :name, presence: true, length: { maximum: 30 }
  validates :role, presence: true

  def entity_properties
    %w[email enabled name role]
  end

  def set_default_values
    default_property_value :enabled, true
    default_property_value :role, 1
  end

  def format_values
    format_property_value :role, :integer
  end
end

Using `attr_accessor` the attributes of the model are defined. Validations and Callbacks all work as you would expect. However, `entity_properties` is new. Data objects in Google Cloud Datastore are known as entities. Entities are of a kind. An entity has one or more named properties, each of which can have one or more values. Think of them like this:

The `entity_properties` method defines an Array of the properties that belong to the entity in cloud datastore. With this approach, Rails deals solely with ActiveModel objects. The objects are converted to/from entities as needed during save/query operations.

We have also added the ability to set default property values and type cast the format of values for entities.

Now on to the controller! A scaffold generated controller works out of the box:

class UsersController < ApplicationController
  before_action :set_user, only: [:show, :edit, :update, :destroy]

  def index
    @users = User.all
  end

  def show
  end

  def new
    @user = User.new
  end

  def edit
  end

  def create
    @user = User.new(user_params)
    respond_to do |format|
      if @user.save
        format.html { redirect_to @user, notice: 'User was successfully created.' }
      else
        format.html { render :new }
      end
    end
  end

  def update
    respond_to do |format|
      if @user.update(user_params)
        format.html { redirect_to @user, notice: 'User was successfully updated.' }
      else
        format.html { render :edit }
      end
    end
  end

  def destroy
    @user.destroy
    respond_to do |format|
      format.html { redirect_to users_url, notice: 'User was successfully destroyed.' }
    end
  end

  private

  def set_user
    @user = User.find(params[:id])
  end

  def user_params
    params.require(:user).permit(:email, :name)
  end
end

Constants

VERSION

Public Instance Methods

build_entity(parent = nil) click to toggle source

Builds the Cloud Datastore entity with attributes from the Model object.

@param [Google::Cloud::Datastore::Key] parent An optional parent Key of the entity.

@return [Entity] The updated Google::Cloud::Datastore::Entity.

# File lib/active_model/datastore.rb, line 153
def build_entity(parent = nil)
  entity = CloudDatastore.dataset.entity self.class.name, id
  if parent.present?
    raise ArgumentError, 'Must be a Key' unless parent.is_a? Google::Cloud::Datastore::Key

    entity.key.parent = parent
  elsif parent?
    entity.key.parent = self.class.parent_key(parent_key_id)
  end
  entity_properties.each do |attr|
    entity[attr] = instance_variable_get("@#{attr}")
    entity.exclude_from_indexes!(attr, true) if no_index_attributes.include? attr
  end
  entity
end
destroy() click to toggle source
# File lib/active_model/datastore.rb, line 190
def destroy
  run_callbacks :destroy do
    key = CloudDatastore.dataset.key self.class.name, id
    key.parent = self.class.parent_key(parent_key_id) if parent?
    self.class.retry_on_exception? { CloudDatastore.dataset.delete key }
  end
end
entity_properties() click to toggle source
# File lib/active_model/datastore.rb, line 128
def entity_properties
  []
end
parent?() click to toggle source

Used to determine if the ActiveModel object belongs to an entity group.

# File lib/active_model/datastore.rb, line 135
def parent?
  parent_key_id.present?
end
persisted?() click to toggle source

Used by ActiveModel for determining polymorphic routing.

# File lib/active_model/datastore.rb, line 142
def persisted?
  id.present?
end
save(parent = nil) click to toggle source
# File lib/active_model/datastore.rb, line 169
def save(parent = nil)
  save_entity(parent)
end
save!() click to toggle source

For compatibility with libraries that require the bang method version (example, factory_bot).

# File lib/active_model/datastore.rb, line 176
def save!
  save_entity || raise(EntityNotSavedError, 'Failed to save the entity')
end
update(params) click to toggle source
# File lib/active_model/datastore.rb, line 180
def update(params)
  assign_attributes(params)
  return unless valid?

  run_callbacks :update do
    entity = build_entity
    self.class.retry_on_exception? { CloudDatastore.dataset.save entity }
  end
end

Private Instance Methods

save_entity(parent = nil) click to toggle source
# File lib/active_model/datastore.rb, line 200
def save_entity(parent = nil)
  return unless valid?

  run_callbacks :save do
    entity = build_entity(parent)
    success = self.class.retry_on_exception? { CloudDatastore.dataset.save entity }
    self.id = entity.key.id if success
    self.parent_key_id = entity.key.parent.id if entity.key.parent.present?
    success
  end
end