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.9.0'

Constants included from NestedAttr

NestedAttr::UNASSIGNABLE_KEYS

Class Method Summary collapse

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

Class Method Details

.loggerObject



122
123
124
# File 'lib/active_model/datastore.rb', line 122

def self.logger
  @logger || (Rails.logger if defined?(Rails) == 'constant')
end

.logger=(logger) ⇒ Object



126
127
128
# File 'lib/active_model/datastore.rb', line 126

def self.logger=(logger)
  @logger = logger
end

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.



161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
# File 'lib/active_model/datastore.rb', line 161

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



198
199
200
201
202
203
204
# File 'lib/active_model/datastore.rb', line 198

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?(operation: 'delete') { CloudDatastore.dataset.delete key }
  end
end

#entity_propertiesObject



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

def entity_properties
  []
end

#parent?Boolean

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

Returns:

  • (Boolean)


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

def parent?
  parent_key_id.present?
end

#persisted?Boolean

Used by ActiveModel for determining polymorphic routing.

Returns:

  • (Boolean)


150
151
152
# File 'lib/active_model/datastore.rb', line 150

def persisted?
  id.present?
end

#save(parent = nil) ⇒ Object



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

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

#save!Object

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



184
185
186
# File 'lib/active_model/datastore.rb', line 184

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

#update(params) ⇒ Object



188
189
190
191
192
193
194
195
196
# File 'lib/active_model/datastore.rb', line 188

def update(params)
  assign_attributes(params)
  return unless valid?

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