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


184
185
186
187
188
189
190
# File 'lib/dynamoid/persistence.rb', line 184

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


203
204
205
206
207
208
209
# File 'lib/dynamoid/persistence.rb', line 203

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


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

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(table_name, attribute)
  end
end

#delete_tableObject

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.


125
126
127
# File 'lib/dynamoid/persistence.rb', line 125

def delete_table
  Dynamoid.adapter.delete_table(table_name)
end

#from_database(attrs = {}) ⇒ Object


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

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


158
159
160
# File 'lib/dynamoid/persistence.rb', line 158

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

#inc(hash_key_value, range_key_value = nil, counters) ⇒ Object

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)

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.

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


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

def inc(hash_key_value, range_key_value = nil, counters)
  options = if range_key
              value_casted = TypeCasting.cast_field(range_key_value, attributes[range_key])
              value_dumped = Dumping.dump_field(value_casted, attributes[range_key])
              { range_key: value_dumped }
            else
              {}
            end

  Dynamoid.adapter.update_item(table_name, hash_key_value, options) do |t|
    counters.each do |k, v|
      value_casted = TypeCasting.cast_field(v, attributes[k])
      value_dumped = Dumping.dump_field(value_casted, attributes[k])

      t.add(k => value_dumped)
    end
  end
end

#table_nameObject


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

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:


226
227
228
229
230
# File 'lib/dynamoid/persistence.rb', line 226

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:


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

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.

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:


288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
# File 'lib/dynamoid/persistence.rb', line 288

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.

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:


339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
# File 'lib/dynamoid/persistence.rb', line 339

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