Class: DynamodbModel::Item

Inherits:
Object
  • Object
show all
Includes:
DbConfig, Log
Defined in:
lib/dynamodb_model/item.rb

Class Method Summary collapse

Instance Method Summary collapse

Methods included from DbConfig

#db, included, #namespaced_table_name

Methods included from Log

included, #log

Constructor Details

#initialize(attrs = {}) ⇒ Item

Returns a new instance of Item.



43
44
45
# File 'lib/dynamodb_model/item.rb', line 43

def initialize(attrs={})
  @attrs = attrs
end

Class Method Details

.delete(key_object, options = {}) ⇒ Object

Two ways to use the delete method:

  1. Specify the key as a String. In this case the key will is the partition_key

set on the model.

MyModel.delete("728e7b5df40b93c3ea6407da8ac3e520e00d7351")
  1. Specify the key as a Hash, you can arbitrarily specific the key structure this way

MyModel.delete(“728e7b5df40b93c3ea6407da8ac3e520e00d7351”)

options is provided in case you want to specific condition_expression or expression_attribute_values.



239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
# File 'lib/dynamodb_model/item.rb', line 239

def self.delete(key_object, options={})
  if key_object.is_a?(String)
    key = {
      partition_key => key_object
    }
  else # it should be a Hash
    key = key_object
  end

  params = {
    table_name: table_name,
    key: key
  }
  # In case you want to specify condition_expression or expression_attribute_values
  params = params.merge(options)

  resp = db.delete_item(params)
end

.find(id) ⇒ Object



219
220
221
222
223
224
225
226
# File 'lib/dynamodb_model/item.rb', line 219

def self.find(id)
  resp = db.get_item(
    table_name: table_name,
    key: {partition_key => id}
  )
  attributes = resp.item # unwraps the item's attributes
  self.new(attributes) if attributes
end

.get_table_nameObject



281
282
283
284
# File 'lib/dynamodb_model/item.rb', line 281

def self.get_table_name
  @table_name ||= self.name.pluralize.underscore
  [table_namespace, @table_name].reject {|s| s.nil? || s.empty?}.join('-')
end

.partition_key(*args) ⇒ Object

When called with an argument we’ll set the internal @partition_key value When called without an argument just retun it. class Comment < DynamodbModel::Item

partition_key "post_id"

end



263
264
265
266
267
268
269
270
# File 'lib/dynamodb_model/item.rb', line 263

def self.partition_key(*args)
  case args.size
  when 0
    @partition_key || "id" # defaults to id
  when 1
    @partition_key = args[0].to_s
  end
end

.query(params = {}) ⇒ Object

Adds very little wrapper logic to query.

  • Automatically add table_name to options for convenience.

  • Decorates return value. Returns Array of [MyModel.new] instead of the dynamodb client response.

Other than that, usage is same was using the dynamodb client query method directly. Example:

MyModel.query(
  index_name: 'category-index',
  expression_attribute_names: { "#category_name" => "category" },
  expression_attribute_values: { ":category_value" => "Entertainment" },
  key_condition_expression: "#category_name = :category_value",
)

AWS Docs examples: docs.aws.amazon.com/amazondynamodb/latest/developerguide/GettingStarted.Ruby.04.html



148
149
150
151
152
# File 'lib/dynamodb_model/item.rb', line 148

def self.query(params={})
  params = { table_name: table_name }.merge(params)
  resp = db.query(params)
  resp.items.map {|i| self.new(i) }
end

.replace(attrs) ⇒ Object



197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
# File 'lib/dynamodb_model/item.rb', line 197

def self.replace(attrs)
  # Automatically adds some attributes:
  #   partition key unique id
  #   created_at and updated_at timestamps. Timestamp format from AWS docs: http://amzn.to/2z98Bdc
  defaults = {
    partition_key => Digest::SHA1.hexdigest([Time.now, rand].join)
  }
  item = defaults.merge(attrs)
  item["created_at"] ||= Time.now.utc.strftime('%Y-%m-%dT%TZ')
  item["updated_at"] = Time.now.utc.strftime('%Y-%m-%dT%TZ')

  # put_item full replaces the item
  resp = db.put_item(
    table_name: table_name,
    item: item
  )

  # The resp does not contain the attrs. So might as well return
  # the original item with the generated partition_key value
  item
end

.scan(params = {}) ⇒ Object

Adds very little wrapper logic to scan.

  • Automatically add table_name to options for convenience.

  • Decorates return value. Returns Array of [MyModel.new] instead of the dynamodb client response.

Other than that, usage is same was using the dynamodb client scan method directly. Example:

MyModel.scan(
  expression_attribute_names: {"#updated_at"=>"updated_at"},
  filter_expression: "#updated_at between :start_time and :end_time",
  expression_attribute_values: {
    ":start_time" => "2010-01-01T00:00:00",
    ":end_time" => "2020-01-01T00:00:00"
  }
)

AWS Docs examples: docs.aws.amazon.com/amazondynamodb/latest/developerguide/GettingStarted.Ruby.04.html



124
125
126
127
128
129
# File 'lib/dynamodb_model/item.rb', line 124

def self.scan(params={})
  log("It's recommended to not use scan for production. It can be slow and expensive. You can a LSI or GSI and query the index instead.")
  params = { table_name: table_name }.merge(params)
  resp = db.scan(params)
  resp.items.map {|i| self.new(i) }
end

.set_table_name(value) ⇒ Object



286
287
288
# File 'lib/dynamodb_model/item.rb', line 286

def self.set_table_name(value)
  @table_name = value
end

.table_name(*args) ⇒ Object



272
273
274
275
276
277
278
279
# File 'lib/dynamodb_model/item.rb', line 272

def self.table_name(*args)
  case args.size
  when 0
    get_table_name
  when 1
    set_table_name(args[0])
  end
end

.where(attributes, options = {}) ⇒ Object

Translates simple query searches:

Post.where({category: "Drama"}, index_name: "category-index")

translates to

resp = db.query(
  table_name: "demo-dev-post",
  index_name: 'category-index',
  expression_attribute_names: { "#category_name" => "category" },
  expression_attribute_values: { ":category_value" => category },
  key_condition_expression: "#category_name = :category_value",
)

TODO: Implement nicer where syntax with index_name as a chained method.

Post.where({category: "Drama"}, {index_name: "category-index"})
  VS
Post.where(category: "Drama").index_name("category-index")


173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
# File 'lib/dynamodb_model/item.rb', line 173

def self.where(attributes, options={})
  raise "attributes.size == 1 only supported for now" if attributes.size != 1

  attr_name = attributes.keys.first
  attr_value = attributes[attr_name]

  # params = {
  #   expression_attribute_names: { "#category_name" => "category" },
  #   expression_attribute_values: { ":category_value" => "Entertainment" },
  #   key_condition_expression: "#category_name = :category_value",
  # }
  name_key, value_key = "##{attr_name}_name", ":#{attr_name}_value"
  params = {
    expression_attribute_names: { name_key => attr_name },
    expression_attribute_values: { value_key => attr_value },
    key_condition_expression: "#{name_key} = #{value_key}",
  }
  # Allow direct access to override params passed to dynamodb query options.
  # This is is how index_name is passed:
  params = params.merge(options)

  query(params)
end

Instance Method Details

#as_json(options = {}) ⇒ Object

For render json: item



91
92
93
# File 'lib/dynamodb_model/item.rb', line 91

def as_json(options={})
  @attrs
end

#attributesObject



101
102
103
# File 'lib/dynamodb_model/item.rb', line 101

def attributes
  @attributes
end

#attributes=(attributes) ⇒ Object

Longer hand methods for completeness. Internallly encourage the shorter attrs method.



97
98
99
# File 'lib/dynamodb_model/item.rb', line 97

def attributes=(attributes)
  @attributes = attributes
end

#attrs(*args) ⇒ Object

Defining our own reader so we can do a deep merge if user passes in attrs



48
49
50
51
52
53
54
55
56
57
58
59
60
# File 'lib/dynamodb_model/item.rb', line 48

def attrs(*args)
  case args.size
  when 0
    ActiveSupport::HashWithIndifferentAccess.new(@attrs)
  when 1
    attributes = args[0] # Hash
    if attributes.empty?
      ActiveSupport::HashWithIndifferentAccess.new
    else
      @attrs = attrs.deep_merge!(attributes)
    end
  end
end

#deleteObject



78
79
80
# File 'lib/dynamodb_model/item.rb', line 78

def delete
  self.class.delete(@attrs[:id]) if @attrs[:id]
end

#find(id) ⇒ Object



74
75
76
# File 'lib/dynamodb_model/item.rb', line 74

def find(id)
  self.class.find(id)
end

#partition_keyObject



86
87
88
# File 'lib/dynamodb_model/item.rb', line 86

def partition_key
  self.class.partition_key
end

#replaceObject

The method is named replace to clearly indicate that the item is fully replaced.



69
70
71
72
# File 'lib/dynamodb_model/item.rb', line 69

def replace
  attrs = self.class.replace(@attrs)
  @attrs = attrs # refresh attrs because it now has the id
end

#table_nameObject



82
83
84
# File 'lib/dynamodb_model/item.rb', line 82

def table_name
  self.class.table_name
end