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 into 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' }])

Instantiates a model and pass it into an optional 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 a model

  • block (Proc)

    Block to process a document after initialization

Returns:

Since:

  • 0.2.0



192
193
194
195
196
197
198
# File 'lib/dynamoid/persistence.rb', line 192

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 into DynamoDB.

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

Raises an exception Dynamoid::Errors::DocumentNotValid if validation failed.

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

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

Instantiates a model and pass it into an optional 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 with which to create the object.

  • block (Proc)

    Block to process a document after initialization

Returns:

Since:

  • 0.2.0



227
228
229
230
231
232
233
# File 'lib/dynamoid/persistence.rb', line 227

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



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

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



131
132
133
134
# File 'lib/dynamoid/persistence.rb', line 131

def delete_table
  Dynamoid.adapter.delete_table(table_name)
  self
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



165
166
167
# File 'lib/dynamoid/persistence.rb', line 165

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



428
429
430
431
# File 'lib/dynamoid/persistence.rb', line 428

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

#table_nameObject



30
31
32
33
34
# File 'lib/dynamoid/persistence.rb', line 30

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:



250
251
252
253
254
# File 'lib/dynamoid/persistence.rb', line 250

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:



273
274
275
276
277
# File 'lib/dynamoid/persistence.rb', line 273

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:



319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
# File 'lib/dynamoid/persistence.rb', line 319

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 declared in the model class.

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:



377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
# File 'lib/dynamoid/persistence.rb', line 377

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