diff --git a/lib/dynomite/item.rb b/lib/dynomite/item.rb index ab9b778..348ef4a 100644 --- a/lib/dynomite/item.rb +++ b/lib/dynomite/item.rb @@ -94,8 +94,13 @@ def update_attribute(field, value) end def delete_attribute(field) - @attrs.delete(field.to_sym) - update(@attrs, {validate: false}) + update_params = { + expression_attribute_names: { "#field_0" => field.to_s }, + update_expression: "REMOVE #field_0", + key: self.primary_key, + table_name: self.class.table_name + } + self.class.client.update_item(update_params) valid? # ActiveRecord does not have a delete_attribute. Follow update_attribute behavior. end alias :remove_attribute :delete_attribute diff --git a/lib/dynomite/item/indexes/finder.rb b/lib/dynomite/item/indexes/finder.rb index 61cf684..1930e02 100644 --- a/lib/dynomite/item/indexes/finder.rb +++ b/lib/dynomite/item/indexes/finder.rb @@ -29,11 +29,7 @@ def find_primary_key_index end def primary_key_found? - if @source.composite_key? - query_fields.include?(@source.partition_key_field) && query_fields.include?(@source.sort_key_field) - else - query_fields.include?(@source.partition_key_field) - end + query_fields.include?(@source.partition_key_field) end # It's possible to have multiple indexes with the same partition and sort key. diff --git a/lib/dynomite/item/typecaster.rb b/lib/dynomite/item/typecaster.rb index 5b0cff3..8038e85 100644 --- a/lib/dynomite/item/typecaster.rb +++ b/lib/dynomite/item/typecaster.rb @@ -30,7 +30,7 @@ def dump(data, depth=0) # The method also helps keep track of where we cast_to_type # It's only a few spots this provides an easy to search for it. # See: https://v5.docs.rubyonjets.com/docs/database/dynamodb/model/typecasting/ - FALSEY = [false, 'false', 'FALSE', 0, '0', 'f', 'F', 'off', 'OFF'] + FALSEY = [nil, false, 'false', 'FALSE', 0, '0', 'f', 'F', 'off', 'OFF'] def cast_to_type(type, value, on: :read) case type when :integer diff --git a/lib/dynomite/item/write/put_item.rb b/lib/dynomite/item/write/put_item.rb index c20f896..9c99c9e 100644 --- a/lib/dynomite/item/write/put_item.rb +++ b/lib/dynomite/item/write/put_item.rb @@ -49,7 +49,7 @@ def permitted_attrs end def handle_conditional_check_failed_exception(exception) - if @params[:condition_expression] == check_unique_condition + if @params[:condition_expression] == check_unique_params[:condition_expression] raise Dynomite::Error::RecordNotUnique.new(not_unique_message) else # currently only other case is locking raise Dynomite::Error::StaleObject.new(exception.message) @@ -62,24 +62,26 @@ def not_unique_message "A #{@model.class.name} with the primary key #{primary_key_attrs} already exists" end + # https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.ConditionExpressions.html#Expressions.ConditionExpressions.PreventingOverwrites + # Examples: + # attribute_not_exists(id) + # attribute_not_exists(category) AND attribute_not_exists(sku) def check_unique_params if @model.new_record? && !@options[:put] - @params.merge!(condition_expression: check_unique_condition) + condition_expression = @model.primary_key_fields.map do |field| + "attribute_not_exists(##{field})" + end.join(" AND ") + expression_attribute_names = {}.tap do |attribute_names| + @model.primary_key_fields.each do |field| + attribute_names["##{field}"] = field + end + end + { condition_expression: condition_expression, expression_attribute_names: expression_attribute_names } else {} end end - # https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.ConditionExpressions.html#Expressions.ConditionExpressions.PreventingOverwrites - # Examples: - # attribute_not_exists(id) - # attribute_not_exists(category) AND attribute_not_exists(sku) - def check_unique_condition - condition_expression = @model.primary_key_fields.map do |field| - "attribute_not_exists(#{field})" - end.join(" AND ") - end - def locking_params return {} if @params[:condition_expression] # already set from check_unique_params return {} unless @model.class.locking_enabled?