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



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

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



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

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



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

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: table_name, attribute: 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.



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

def delete_table
  Dynamoid.adapter.delete_table(table_name)
end

#from_database(attrs = {}) ⇒ Object



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

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



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

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



370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
# File 'lib/dynamoid/persistence.rb', line 370

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



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

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:



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

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:



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

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.

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:



284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
# File 'lib/dynamoid/persistence.rb', line 284

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.

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:



332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
# File 'lib/dynamoid/persistence.rb', line 332

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