Class: Dynomite::Item::Write::PutItem

Inherits:
Base
  • Object
show all
Defined in:
lib/dynomite/item/write/put_item.rb

Instance Method Summary collapse

Methods inherited from Base

call, #initialize

Constructor Details

This class inherits a constructor from Dynomite::Item::Write::Base

Instance Method Details

#callObject



3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# File 'lib/dynomite/item/write/put_item.rb', line 3

def call
  # typecaster will convert the attrs to the correct types for saving to DynamoDB
  item = Dynomite::Item::Typecaster.new(@model).dump(permitted_attrs)
  @params = {
    table_name: @model.class.table_name,
    item: item
  }
  @params.merge!(check_unique_params)
  @params.merge!(locking_params)

  # put_item replaces the item fully. The resp does not contain the attrs.
  log_debug(@params)
  begin
    client.put_item(@params)
  rescue Aws::DynamoDB::Errors::ConditionalCheckFailedException => e
    handle_conditional_check_failed_exception(e)
  rescue Aws::DynamoDB::Errors::ValidationException => e
    @model.reset_lock_version_was if @model.class.locking_enabled?
    raise
  end

  @model.new_record = false
  @model
end

#check_unique_conditionObject



77
78
79
80
81
# File 'lib/dynomite/item/write/put_item.rb', line 77

def check_unique_condition
  condition_expression = @model.primary_key_fields.map do |field|
    "attribute_not_exists(#{field})"
  end.join(" AND ")
end

#check_unique_paramsObject



65
66
67
68
69
70
71
# File 'lib/dynomite/item/write/put_item.rb', line 65

def check_unique_params
  if @model.new_record? && !@options[:put]
    @params.merge!(condition_expression: check_unique_condition)
  else
    {}
  end
end

#handle_conditional_check_failed_exception(exception) ⇒ Object



51
52
53
54
55
56
57
# File 'lib/dynomite/item/write/put_item.rb', line 51

def handle_conditional_check_failed_exception(exception)
  if @params[:condition_expression] == check_unique_condition
    raise Dynomite::Error::RecordNotUnique.new(not_unique_message)
  else # currently only other case is locking
    raise Dynomite::Error::StaleObject.new(exception.message)
  end
end

#locking_paramsObject



83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
# File 'lib/dynomite/item/write/put_item.rb', line 83

def locking_params
  return {} if @params[:condition_expression] # already set from check_unique_params
  return {} unless @model.class.locking_enabled?
  return {} if @model._touching
  field = @model.class.locking_field_name
  current_version = @model.send(field) # must use send, since it was set by send. fixes .touch method
  return {} if current_version == 1

  previous_version = current_version - 1 # since before_save increments it
  {
    condition_expression: "#lock_version = :lock_version",
    expression_attribute_names: {"#lock_version" => "lock_version"},
    expression_attribute_values: {":lock_version" => previous_version}
  }
end

#not_unique_messageObject



59
60
61
62
63
# File 'lib/dynomite/item/write/put_item.rb', line 59

def not_unique_message
  primary_key_attrs = permitted_attrs.stringify_keys.slice(@model.partition_key_field, @model.sort_key_field).symbolize_keys
  primary_key_found = primary_key_attrs.keys.map(&:to_s)
  "A #{@model.class.name} with the primary key #{primary_key_attrs} already exists"
end

#permitted_attrsObject



28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# File 'lib/dynomite/item/write/put_item.rb', line 28

def permitted_attrs
  field_names = @model.class.field_names.map(&:to_sym)
  assigned_fields = @model.attrs.keys.map(&:to_sym)
  undeclared_fields = assigned_fields - field_names
  declared_fields = field_names - assigned_fields

  case Dynomite.config.undeclared_field_behavior.to_sym
  when :allow
    @model.attrs # allow
  when :silent
    @model.attrs.slice(*field_names)
  when :error
    unless undeclared_fields.empty?
      raise Dynomite::Error::UndeclaredFields.new("ERROR: Saving undeclared fields not allowed: #{undeclared_fields} for #{@model.class}")
    end
  else # warn
    unless undeclared_fields.empty?
      logger.info "WARNING: Not saving undeclared fields: #{undeclared_fields}. Saving declared fields only: #{declared_fields} for #{@model.class}"
    end
    @model.attrs.slice(*field_names)
  end
end