Module: Dynamoid::Persistence::ClassMethods
- Defined in:
- lib/dynamoid/persistence.rb
Instance Method Summary collapse
-
#create(attrs = {}, &block) ⇒ Dynamoid::Document
Create a model.
-
#create!(attrs = {}, &block) ⇒ Dynamoid::Document
Create a model.
-
#create_table(options = {}) ⇒ true|false
Create a table.
-
#delete(*ids) ⇒ Object
Delete a model by a given primary key.
-
#delete_table ⇒ Model class
Deletes the table for the model.
- #from_database(attrs = {}) ⇒ Object
-
#import(array_of_attributes) ⇒ Array
Create several models at once.
-
#inc(hash_key_value, range_key_value = nil, counters) ⇒ Model class
Increase a numeric field by specified value.
- #table_name ⇒ Object
-
#update(hash_key, range_key_value = nil, attrs) ⇒ Dynamoid::Document
Update document with provided attributes.
-
#update!(hash_key, range_key_value = nil, attrs) ⇒ Dynamoid::Document
Update document with provided attributes.
-
#update_fields(hash_key_value, range_key_value = nil, attrs = {}, conditions = {}) ⇒ Dynamoid::Document|nil
Update document.
-
#upsert(hash_key_value, range_key_value = nil, attrs = {}, conditions = {}) ⇒ Dynamoid::Document|nil
Update an existing document or create a new one.
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.
Raises Dynamoid::Errors::MissingRangeKey if a sort key is required but not specified or has value nil.
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!(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.
If any of the before_* callbacks throws :abort the creation is cancelled and create! raises Dynamoid::Errors::RecordNotSaved.
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.
Raises Dynamoid::Errors::MissingRangeKey if a sort key is required but not specified or has value nil.
243 244 245 246 247 248 249 |
# File 'lib/dynamoid/persistence.rb', line 243 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.
104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 |
# File 'lib/dynamoid/persistence.rb', line 104 def create_table( = {}) range_key_hash = if range_key { range_key => PrimaryKeyTypeMapping.dynamodb_type(attributes[range_key][:type], attributes[range_key]) } end = { 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() created_successfuly = Dynamoid.adapter.create_table([:table_name], [:id], ) if created_successfuly && self.[:expires] attribute = self.[:expires][:field] Dynamoid.adapter.update_time_to_live([:table_name], attribute) end self end |
#delete(*ids) ⇒ Object
Delete a model by a given primary key.
Raises Dynamoid::Errors::MissingHashKey if a partition key has value nil and raises Dynamoid::Errors::MissingRangeKey if a sort key is required but has value nil or is missing.
482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 |
# File 'lib/dynamoid/persistence.rb', line 482 def delete(*ids) if ids.empty? raise Dynamoid::Errors::MissingHashKey end if ids[0].is_a?(Array) # given multiple keys # Model.delete([id1, id2, id3]) keys = ids[0] # ignore other arguments ids = [] if self.range_key # compound primary key # expect [hash key, range key] pairs range_key = [] # assume all elements are pairs, that's arrays keys.each do |pk, sk| raise Dynamoid::Errors::MissingHashKey if pk.nil? raise Dynamoid::Errors::MissingRangeKey if self.range_key && sk.nil? partition_key_dumped = cast_and_dump(hash_key, pk) sort_key_dumped = cast_and_dump(self.range_key, sk) ids << partition_key_dumped range_key << sort_key_dumped end else # simple primary key range_key = nil keys.each do |pk| raise Dynamoid::Errors::MissingHashKey if pk.nil? partition_key_dumped = cast_and_dump(hash_key, pk) ids << partition_key_dumped end end = range_key ? { range_key: range_key } : {} Dynamoid.adapter.delete(table_name, ids, ) else # given single primary key: # Model.delete(partition_key) # Model.delete(partition_key, sort_key) partition_key, sort_key = ids raise Dynamoid::Errors::MissingHashKey if partition_key.nil? raise Dynamoid::Errors::MissingRangeKey if range_key? && sort_key.nil? = sort_key ? { range_key: cast_and_dump(self.range_key, sort_key) } : {} partition_key_dumped = cast_and_dump(hash_key, partition_key) Dynamoid.adapter.delete(table_name, partition_key_dumped, ) end nil end |
#delete_table ⇒ Model 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.
138 139 140 141 |
# File 'lib/dynamoid/persistence.rb', line 138 def delete_table Dynamoid.adapter.delete_table(table_name) self end |
#from_database(attrs = {}) ⇒ Object
144 145 146 147 148 |
# File 'lib/dynamoid/persistence.rb', line 144 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.
172 173 174 |
# File 'lib/dynamoid/persistence.rb', line 172 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])
456 457 458 459 460 |
# File 'lib/dynamoid/persistence.rb', line 456 def inc(hash_key_value, range_key_value = nil, counters) # It's similar to Rails' #update_counters. Inc.call(self, hash_key_value, range_key_value, counters) self end |
#table_name ⇒ Object
30 31 32 33 34 35 36 37 38 39 40 41 |
# File 'lib/dynamoid/persistence.rb', line 30 def table_name return @table_name if @table_name if [:arn] @table_name = [:arn] return @table_name end base_name = [:name] || base_class.name.split('::').last.downcase.pluralize namespace = Dynamoid::Config.namespace.to_s @table_name = [namespace, 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)
266 267 268 269 270 |
# File 'lib/dynamoid/persistence.rb', line 266 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.
289 290 291 292 293 |
# File 'lib/dynamoid/persistence.rb', line 289 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
Raises Dynamoid::Errors::MissingHashKey if a partition key has value nil and Dynamoid::Errors::MissingRangeKey if a sort key is required but has value nil.
341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 |
# File 'lib/dynamoid/persistence.rb', line 341 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.
Raises Dynamoid::Errors::MissingHashKey if a partition key has value nil and raises Dynamoid::Errors::MissingRangeKey if a sort key is required but has value nil.
405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 |
# File 'lib/dynamoid/persistence.rb', line 405 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 |