Module: ActiveModel::Datastore

Extended by:
ActiveSupport::Concern
Includes:
ExcludedIndexes, NestedAttr, PropertyValues, TrackChanges, Dirty, Model, Validations, Validations::Callbacks
Defined in:
lib/active_model/datastore.rb,
lib/active_model/datastore/version.rb,
lib/active_model/datastore/nested_attr.rb,
lib/active_model/datastore/track_changes.rb,
lib/active_model/datastore/property_values.rb,
lib/active_model/datastore/excluded_indexes.rb,
lib/active_model/datastore/errors.rb

Overview

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:

  • ‘Kind’ (which is your table)

  • ‘Entity’ (which is the record from the table)

  • ‘Property’ (which is the attribute of the record)

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

Defined Under Namespace

Modules: ClassMethods, ExcludedIndexes, NestedAttr, PropertyValues, TrackChanges Classes: EntityError, EntityNotSavedError, Error, TrackChangesError

Constant Summary collapse

VERSION =
'0.8.0'

Instance Method Summary collapse

Methods included from TrackChanges

#exclude_from_save?, #reload!, #remove_unmodified_children, #tracked_attributes, #values_changed?

Methods included from PropertyValues

#default_property_value, #format_property_value

Methods included from NestedAttr

#assign_nested_attributes, #mark_for_destruction, #marked_for_destruction?, #nested_attributes?, #nested_errors, #nested_model_class_names, #nested_models

Methods included from ExcludedIndexes

#exclude_from_index, #no_index_attributes

Instance Method Details

#build_entity(parent = nil) ⇒ Entity

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

Parameters:

  • parent (Google::Cloud::Datastore::Key) (defaults to: nil)

    An optional parent Key of the entity.

Returns:

  • (Entity)

    The updated Google::Cloud::Datastore::Entity.



153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
# 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

#destroyObject



190
191
192
193
194
195
196
# 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_propertiesObject



128
129
130
# File 'lib/active_model/datastore.rb', line 128

def entity_properties
  []
end

#parent?Boolean

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

Returns:

  • (Boolean)


135
136
137
# File 'lib/active_model/datastore.rb', line 135

def parent?
  parent_key_id.present?
end

#persisted?Boolean

Used by ActiveModel for determining polymorphic routing.

Returns:

  • (Boolean)


142
143
144
# File 'lib/active_model/datastore.rb', line 142

def persisted?
  id.present?
end

#save(parent = nil) ⇒ Object



169
170
171
# File 'lib/active_model/datastore.rb', line 169

def save(parent = nil)
  save_entity(parent)
end

#save!Object

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



176
177
178
# File 'lib/active_model/datastore.rb', line 176

def save!
  save_entity || raise(EntityNotSavedError, 'Failed to save the entity')
end

#update(params) ⇒ Object



180
181
182
183
184
185
186
187
188
# 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