Module: ActiveModel::Datastore

Extended by:
ActiveSupport::Concern
Includes:
NestedAttr, 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/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, :name, :enabled, :state

  before_validation :set_default_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 }

  def entity_properties
    %w[email name enabled]
  end

  def set_default_values
    default_property_value :enabled, true
  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, NestedAttr, TrackChanges Classes: EntityError, EntityNotSavedError, Error, TrackChangesError

Constant Summary collapse

VERSION =
'0.1.0'

Instance Method Summary collapse

Methods included from TrackChanges

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

Methods included from NestedAttr

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

Instance Method Details

#build_entity(parent = nil) ⇒ Entity

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

Returns:

  • (Entity)

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



182
183
184
185
186
187
188
189
# File 'lib/active_model/datastore.rb', line 182

def build_entity(parent = nil)
  entity = CloudDatastore.dataset.entity self.class.name, id
  entity.key.parent = parent if parent.present?
  entity_properties.each do |attr|
    entity[attr] = instance_variable_get("@#{attr}")
  end
  entity
end

#default_property_value(attr, value) ⇒ Object

Sets a default value for the property if not currently set.

Example:

default_property_value :state, 0

is equivalent to:

self.state = state.presence || 0

Example:

default_property_value :enabled, false

is equivalent to:

self.enabled = false if enabled.nil?


148
149
150
151
152
153
154
# File 'lib/active_model/datastore.rb', line 148

def default_property_value(attr, value)
  if value.is_a?(TrueClass) || value.is_a?(FalseClass)
    send("#{attr.to_sym}=", value) if send(attr.to_sym).nil?
  else
    send("#{attr.to_sym}=", send(attr.to_sym).presence || value)
  end
end

#destroyObject



222
223
224
225
226
227
# File 'lib/active_model/datastore.rb', line 222

def destroy
  run_callbacks :destroy do
    key = CloudDatastore.dataset.key self.class.name, id
    self.class.retry_on_exception? { CloudDatastore.dataset.delete key }
  end
end

#entity_propertiesObject



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

def entity_properties
  []
end

#format_property_value(attr, type) ⇒ Object

Converts the type of the property.

Example:

format_property_value :weight, :float

is equivalent to:

self.weight = weight.to_f if weight.present?


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

def format_property_value(attr, type)
  return unless send(attr.to_sym).present?
  case type.to_sym
  when :float
    send("#{attr.to_sym}=", send(attr.to_sym).to_f)
  when :integer
    send("#{attr.to_sym}=", send(attr.to_sym).to_i)
  else
    raise ArgumentError, 'Supported types are :float, :integer'
  end
end

#persisted?Boolean

Used by ActiveModel for determining polymorphic routing.

Returns:

  • (Boolean)


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

def persisted?
  id.present?
end

#save(parent = nil) ⇒ Object



191
192
193
# File 'lib/active_model/datastore.rb', line 191

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

#save!Object

For compatibility with libraries that require the bang method version (example, factory_girl). If you require a save! method that supports parents (ancestor queries), override this method in your own code with something like this:

def save!
  parent = nil
  if .present?
    parent = CloudDatastore.dataset.key 'Parent' + self.class.name, .to_i
  end
  msg = 'Failed to save the entity'
  save_entity(parent) || raise(ActiveModel::Datastore::EntityNotSavedError, msg)
end


209
210
211
# File 'lib/active_model/datastore.rb', line 209

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

#update(params) ⇒ Object



213
214
215
216
217
218
219
220
# File 'lib/active_model/datastore.rb', line 213

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