Module: Dynamoid::Persistence::ClassMethods

Defined in:
lib/dynamoid/persistence.rb

Instance Method Summary collapse

Instance Method Details

#create(attrs = {}, &block) ⇒ Dynamoid::Document

Create a model.

Initializes a new model and immediately saves it to DynamoDB.

User.create(first_name: 'Mark', last_name: 'Tyler')

Accepts both Hash and Array of Hashes and can create several models.

User.create([{ first_name: 'Alice' }, { first_name: 'Bob' }])

Creates a model and pass it into a block to set other attributes.

User.create(first_name: 'Mark') do |u|
  u.age = 21
end

Validates model and runs callbacks.

Parameters:

  • attrs (Hash|Array[Hash]) (defaults to: {})

    Attributes of the models

  • block (Proc)

    Block to process a document after initialization

Returns:

Since:

  • 0.2.0



189
190
191
192
193
194
195
# File 'lib/dynamoid/persistence.rb', line 189

def create(attrs = {}, &block)
  if attrs.is_a?(Array)
    attrs.map { |attr| create(attr, &block) }
  else
    build(attrs, &block).tap(&:save)
  end
end

#create!(attrs = {}, &block) ⇒ Dynamoid::Document

Create a model.

Initializes a new object and immediately saves it to the Dynamoid. Raises an exception Dynamoid::Errors::DocumentNotValid if validation failed. Accepts both Hash and Array of Hashes and can create several models.

Parameters:

  • attrs (Hash|Array[Hash]) (defaults to: {})

    Attributes with which to create the object.

  • block (Proc)

    Block to process a document after initialization

Returns:

Since:

  • 0.2.0



208
209
210
211
212
213
214
# File 'lib/dynamoid/persistence.rb', line 208

def create!(attrs = {}, &block)
  if attrs.is_a?(Array)
    attrs.map { |attr| create!(attr, &block) }
  else
    build(attrs, &block).tap(&:save!)
  end
end

#create_table(options = {}) ⇒ true|false

Create a table.

Uses a configuration specified in a model class (with the table method) e.g. table name, schema (hash and range keys), global and local secondary indexes, billing mode and write/read capacity.

For instance here

class User
  include Dynamoid::Document

  table key: :uuid
  range :last_name

  field :first_name
  field :last_name
end

User.create_table

create_table method call will create a table dynamoid_users with hash key uuid and range key name, DynamoDB default billing mode and Dynamoid default read/write capacity units (100/20).

All the configuration can be overridden with options argument.

User.create_table(table_name: 'users', read_capacity: 200, write_capacity: 40)

Dynamoid creates a table synchronously by default. DynamoDB table creation is an asynchronous operation and a client should wait until a table status changes to ACTIVE and a table becomes available. That’s why Dynamoid is polling a table status and returns results only when a table becomes available.

Polling is configured with Dynamoid::Config.sync_retry_max_times and Dynamoid::Config.sync_retry_wait_seconds configuration options. If table creation takes more time than configured waiting time then Dynamoid stops polling and returns true.

In order to return back asynchronous behaviour and not to wait until a table is created the sync: false option should be specified.

User.create_table(sync: false)

Subsequent method calls for the same table will be ignored.

Parameters:

  • options (Hash) (defaults to: {})

Options Hash (options):

  • :table_name (Symbol)

    name of the table

  • :id (Symbol)

    hash key name of the table

  • :hash_key_type (Symbol)

    Dynamoid type of the hash key - :string, :integer or any other scalar type

  • :range_key (Hash)

    a Hash with range key name and type in format { <name> => <type> } e.g. { last_name: :string }

  • :billing_mode (String)

    billing mode of a table - either PROVISIONED (default) or PAY_PER_REQUEST (for On-Demand Mode)

  • :read_capacity (Integer)

    read capacity units for the table; does not work on existing tables and is ignored when billing mode is PAY_PER_REQUEST

  • :write_capacity (Integer)

    write capacity units for the table; does not work on existing tables and is ignored when billing mode is PAY_PER_REQUEST

  • :local_secondary_indexes (Hash)
  • :global_secondary_indexes (Hash)
  • :sync (true|false)

    specifies should the method call be synchronous and wait until a table is completely created

Returns:

  • (true|false)

    Whether a table created successfully

Since:

  • 0.4.0



95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
# File 'lib/dynamoid/persistence.rb', line 95

def create_table(options = {})
  range_key_hash = if range_key
                     { range_key => PrimaryKeyTypeMapping.dynamodb_type(attributes[range_key][:type], attributes[range_key]) }
                   end

  options = {
    id: hash_key,
    table_name: table_name,
    billing_mode: capacity_mode,
    write_capacity: write_capacity,
    read_capacity: read_capacity,
    range_key: range_key_hash,
    hash_key_type: PrimaryKeyTypeMapping.dynamodb_type(attributes[hash_key][:type], attributes[hash_key]),
    local_secondary_indexes: local_secondary_indexes.values,
    global_secondary_indexes: global_secondary_indexes.values
  }.merge(options)

  created_successfuly = Dynamoid.adapter.create_table(options[:table_name], options[:id], options)

  if created_successfuly && self.options[:expires]
    attribute = self.options[:expires][:field]
    Dynamoid.adapter.update_time_to_live(options[:table_name], attribute)
  end

  self
end

#delete_tableModel class

Deletes the table for the model.

Dynamoid deletes a table asynchronously and doesn’t wait until a table is deleted completely.

Subsequent method calls for the same table will be ignored.

Returns:

  • (Model class)

    self



129
130
131
132
# File 'lib/dynamoid/persistence.rb', line 129

def delete_table
  Dynamoid.adapter.delete_table(table_name)
  self
end

#from_database(attrs = {}) ⇒ Object



135
136
137
138
139
# File 'lib/dynamoid/persistence.rb', line 135

def from_database(attrs = {})
  klass = choose_right_class(attrs)
  attrs_undumped = Undumping.undump_attributes(attrs, klass.attributes)
  klass.new(attrs_undumped).tap { |r| r.new_record = false }
end

#import(array_of_attributes) ⇒ Array

Create several models at once.

users = User.import([{ name: 'a' }, { name: 'b' }])

import is a relatively low-level method and bypasses some mechanisms like callbacks and validation.

It sets timestamp fields created_at and updated_at if they are blank. It sets a hash key field as well if it’s blank. It expects that the hash key field is string and sets a random UUID value if the field value is blank. All the field values are type casted to the declared types.

It works efficiently and uses the ‘BatchWriteItem` operation. In order to cope with throttling it uses a backoff strategy if it’s specified with ‘Dynamoid::Config.backoff` configuration option.

Because of the nature of DynamoDB and its limits only 25 models can be saved at once. So multiple HTTP requests can be sent to DynamoDB.

Parameters:

  • array_of_attributes (Array<Hash>)

Returns:

  • (Array)

    Created models



163
164
165
# File 'lib/dynamoid/persistence.rb', line 163

def import(array_of_attributes)
  Import.call(self, array_of_attributes)
end

#inc(hash_key_value, range_key_value = nil, counters) ⇒ Model class

Increase a numeric field by specified value.

User.inc('1', age: 2)

Can update several fields at once.

User.inc('1', age: 2, version: 1)

If range key is declared for a model it should be passed as well:

User.inc('1', 'Tylor', age: 2)

It’s an atomic operation it does not interfere with other write requests.

Uses efficient low-level UpdateItem operation and does only one HTTP request.

Doesn’t run validations and callbacks. Doesn’t update created_at and updated_at as well.

When ‘:touch` option is passed the timestamp columns are updating. If attribute names are passed, they are updated along with updated_at attribute:

User.inc('1', age: 2, touch: true)
User.inc('1', age: 2, touch: :viewed_at)
User.inc('1', age: 2, touch: [:viewed_at, :accessed_at])

Parameters:

  • hash_key_value (Scalar value)

    hash key

  • range_key_value (Scalar value) (defaults to: nil)

    range key (optional)

  • counters (Hash)

    value to increase by

Options Hash (counters):

  • :touch (true | Symbol | Array[Symbol])

    to update update_at attribute and optionally the specified ones

Returns:

  • (Model class)

    self



409
410
411
412
# File 'lib/dynamoid/persistence.rb', line 409

def inc(hash_key_value, range_key_value = nil, counters)
  Inc.call(self, hash_key_value, range_key_value, counters)
  self
end

#table_nameObject



28
29
30
31
32
# File 'lib/dynamoid/persistence.rb', line 28

def table_name
  table_base_name = options[:name] || base_class.name.split('::').last.downcase.pluralize

  @table_name ||= [Dynamoid::Config.namespace.to_s, table_base_name].reject(&:empty?).join('_')
end

#update(hash_key, range_key_value = nil, attrs) ⇒ Dynamoid::Document

Update document with provided attributes.

Instantiates document and saves changes. Runs validations and callbacks. Don’t save changes if validation fails.

User.update('1', age: 26)

If range key is declared for a model it should be passed as well:

User.update('1', 'Tylor', age: 26)

Parameters:

  • hash_key (Scalar value)

    hash key

  • range_key_value (Scalar value) (defaults to: nil)

    range key (optional)

  • attrs (Hash)

Returns:



231
232
233
234
235
# File 'lib/dynamoid/persistence.rb', line 231

def update(hash_key, range_key_value = nil, attrs)
  model = find(hash_key, range_key: range_key_value, consistent_read: true)
  model.update_attributes(attrs)
  model
end

#update!(hash_key, range_key_value = nil, attrs) ⇒ Dynamoid::Document

Update document with provided attributes.

Instantiates document and saves changes. Runs validations and callbacks.

User.update!('1', age: 26)

If range key is declared for a model it should be passed as well:

User.update('1', 'Tylor', age: 26)

Raises Dynamoid::Errors::DocumentNotValid exception if validation fails.

Parameters:

  • hash_key (Scalar value)

    hash key

  • range_key_value (Scalar value) (defaults to: nil)

    range key (optional)

  • attrs (Hash)

Returns:



254
255
256
257
258
# File 'lib/dynamoid/persistence.rb', line 254

def update!(hash_key, range_key_value = nil, attrs)
  model = find(hash_key, range_key: range_key_value, consistent_read: true)
  model.update_attributes!(attrs)
  model
end

#update_fields(hash_key_value, range_key_value = nil, attrs = {}, conditions = {}) ⇒ Dynamoid::Document|nil

Update document.

Doesn’t run validations and callbacks.

User.update_fields('1', age: 26)

If range key is declared for a model it should be passed as well:

User.update_fields('1', 'Tylor', age: 26)

Can make a conditional update so a document will be updated only if it meets the specified conditions. Conditions can be specified as a Hash with :if key:

User.update_fields('1', { age: 26 }, { if: { version: 1 } })

Here User model has an integer version field and the document will be updated only if the version attribute currently has value 1.

If a document with specified hash and range keys doesn’t exist or conditions were specified and failed the method call returns nil.

To check if some attribute (or attributes) isn’t stored in a DynamoDB item (e.g. it wasn’t set explicitly) there is another condition - unless_exists:

user = User.create(name: 'Tylor')
User.update_fields(user.id, { age: 18 }, { unless_exists: [:age] })

update_fields uses the UpdateItem operation so it saves changes and loads an updated document back with one HTTP request.

Raises a Dynamoid::Errors::UnknownAttribute exception if any of the attributes is not on the model

Parameters:

  • hash_key_value (Scalar value)

    hash key

  • range_key_value (Scalar value) (defaults to: nil)

    range key (optional)

  • attrs (Hash) (defaults to: {})
  • conditions (Hash) (defaults to: {})

    (optional)

Returns:



300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
# File 'lib/dynamoid/persistence.rb', line 300

def update_fields(hash_key_value, range_key_value = nil, attrs = {}, conditions = {})
  optional_params = [range_key_value, attrs, conditions].compact
  if optional_params.first.is_a?(Hash)
    range_key_value = nil
    attrs, conditions = optional_params[0..1]
  else
    range_key_value = optional_params.first
    attrs, conditions = optional_params[1..2]
  end

  UpdateFields.call(self,
                    partition_key: hash_key_value,
                    sort_key: range_key_value,
                    attributes: attrs,
                    conditions: conditions)
end

#upsert(hash_key_value, range_key_value = nil, attrs = {}, conditions = {}) ⇒ Dynamoid::Document|nil

Update an existing document or create a new one.

If a document with specified hash and range keys doesn’t exist it creates a new document with specified attributes. Doesn’t run validations and callbacks.

User.upsert('1', age: 26)

If range key is declared for a model it should be passed as well:

User.upsert('1', 'Tylor', age: 26)

Can make a conditional update so a document will be updated only if it meets the specified conditions. Conditions can be specified as a Hash with :if key:

User.upsert('1', { age: 26 }, { if: { version: 1 } })

Here User model has an integer version field and the document will be updated only if the version attribute currently has value 1.

To check if some attribute (or attributes) isn’t stored in a DynamoDB item (e.g. it wasn’t set explicitly) there is another condition - unless_exists:

user = User.create(name: 'Tylor')
User.upsert(user.id, { age: 18 }, { unless_exists: [:age] })

If conditions were specified and failed the method call returns nil.

upsert uses the UpdateItem operation so it saves changes and loads an updated document back with one HTTP request.

Raises a Dynamoid::Errors::UnknownAttribute exception if any of the attributes is not on the model

Parameters:

  • hash_key_value (Scalar value)

    hash key

  • range_key_value (Scalar value) (defaults to: nil)

    range key (optional)

  • attrs (Hash) (defaults to: {})
  • conditions (Hash) (defaults to: {})

    (optional)

Returns:



358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
# File 'lib/dynamoid/persistence.rb', line 358

def upsert(hash_key_value, range_key_value = nil, attrs = {}, conditions = {})
  optional_params = [range_key_value, attrs, conditions].compact
  if optional_params.first.is_a?(Hash)
    range_key_value = nil
    attrs, conditions = optional_params[0..1]
  else
    range_key_value = optional_params.first
    attrs, conditions = optional_params[1..2]
  end

  Upsert.call(self,
              partition_key: hash_key_value,
              sort_key: range_key_value,
              attributes: attrs,
              conditions: conditions)
end