Class: Dynomite::Item
- Inherits:
-
Object
- Object
- Dynomite::Item
- Defined in:
- lib/dynomite/item.rb
Class Method Summary collapse
- .add_column(name) ⇒ Object
-
.column(*names) ⇒ Object
Defines column.
- .count ⇒ Object
-
.delete(key_object, options = {}) ⇒ Object
Two ways to use the delete method:.
- .find(id) ⇒ Object
- .get_table_name ⇒ Object
-
.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.
-
.query(params = {}) ⇒ Object
Adds very little wrapper logic to query.
- .replace(attrs) ⇒ Object
-
.scan(params = {}) ⇒ Object
Adds very little wrapper logic to scan.
- .set_table_name(value) ⇒ Object
- .table ⇒ Object
- .table_name(*args) ⇒ Object
-
.where(attributes, options = {}) ⇒ Object
Translates simple query searches:.
Instance Method Summary collapse
-
#as_json(options = {}) ⇒ Object
For render json: item.
- #attributes ⇒ Object
-
#attributes=(attributes) ⇒ Object
Longer hand methods for completeness.
-
#attrs(*args) ⇒ Object
Defining our own reader so we can do a deep merge if user passes in attrs.
- #delete ⇒ Object
- #find(id) ⇒ Object
-
#initialize(attrs = {}) ⇒ Item
constructor
A new instance of Item.
- #partition_key ⇒ Object
-
#replace(hash = {}) ⇒ Object
The method is named replace to clearly indicate that the item is fully replaced.
-
#replace!(hash = {}) ⇒ Object
Similar to replace, but raises an error on failed validation.
- #table_name ⇒ Object
Methods included from DbConfig
#db, included, #namespaced_table_name
Methods included from Log
Constructor Details
#initialize(attrs = {}) ⇒ Item
Returns a new instance of Item.
46 47 48 |
# File 'lib/dynomite/item.rb', line 46 def initialize(attrs={}) @attrs = attrs end |
Class Method Details
.add_column(name) ⇒ Object
333 334 335 336 337 338 339 340 341 342 343 344 345 |
# File 'lib/dynomite/item.rb', line 333 def self.add_column(name) if Dynomite::RESERVED_WORDS.include?(name) raise ReservedWordError, "'#{name}' is a reserved word" end define_method(name) do @attrs[name.to_s] end define_method("#{name}=") do |value| @attrs[name.to_s] = value end end |
.column(*names) ⇒ Object
Defines column. Defined column can be accessed by getter and setter methods of the same name (e.g. [model.my_column]). Attributes with undefined columns can be accessed by
- model.attrs
-
method.
328 329 330 |
# File 'lib/dynomite/item.rb', line 328 def self.column(*names) names.each(&method(:add_column)) end |
.count ⇒ Object
321 322 323 |
# File 'lib/dynomite/item.rb', line 321 def self.count table.item_count end |
.delete(key_object, options = {}) ⇒ Object
Two ways to use the delete method:
-
Specify the key as a String. In this case the key will is the partition_key
set on the model.
MyModel.delete("728e7b5df40b93c3ea6407da8ac3e520e00d7351")
-
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.
266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 |
# File 'lib/dynomite/item.rb', line 266 def self.delete(key_object, ={}) 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() resp = db.delete_item(params) end |
.find(id) ⇒ Object
238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 |
# File 'lib/dynomite/item.rb', line 238 def self.find(id) params = case id when String { partition_key => id } when Hash id end resp = db.get_item( table_name: table_name, key: params ) attributes = resp.item # unwraps the item's attributes self.new(attributes) if attributes end |
.get_table_name ⇒ Object
308 309 310 311 |
# File 'lib/dynomite/item.rb', line 308 def self.get_table_name @table_name ||= self.name.pluralize.gsub('::','-').underscore.dasherize [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 < Dynomite::Item
partition_key "post_id"
end
290 291 292 293 294 295 296 297 |
# File 'lib/dynomite/item.rb', line 290 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
167 168 169 170 171 |
# File 'lib/dynomite/item.rb', line 167 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
216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 |
# File 'lib/dynomite/item.rb', line 216 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
142 143 144 145 146 147 148 |
# File 'lib/dynomite/item.rb', line 142 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.") log("Scanning table: #{table_name}") params = { table_name: table_name }.merge(params) resp = db.scan(params) resp.items.map {|i| self.new(i) } end |
.set_table_name(value) ⇒ Object
313 314 315 |
# File 'lib/dynomite/item.rb', line 313 def self.set_table_name(value) @table_name = value end |
.table ⇒ Object
317 318 319 |
# File 'lib/dynomite/item.rb', line 317 def self.table Aws::DynamoDB::Table.new(name: table_name, client: db) end |
.table_name(*args) ⇒ Object
299 300 301 302 303 304 305 306 |
# File 'lib/dynomite/item.rb', line 299 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")
192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 |
# File 'lib/dynomite/item.rb', line 192 def self.where(attributes, ={}) 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() query(params) end |
Instance Method Details
#as_json(options = {}) ⇒ Object
For render json: item
109 110 111 |
# File 'lib/dynomite/item.rb', line 109 def as_json(={}) @attrs end |
#attributes ⇒ Object
119 120 121 |
# File 'lib/dynomite/item.rb', line 119 def attributes @attributes end |
#attributes=(attributes) ⇒ Object
Longer hand methods for completeness. Internallly encourage the shorter attrs method.
115 116 117 |
# File 'lib/dynomite/item.rb', line 115 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
51 52 53 54 55 56 57 58 59 60 61 62 63 |
# File 'lib/dynomite/item.rb', line 51 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 |
#delete ⇒ Object
96 97 98 |
# File 'lib/dynomite/item.rb', line 96 def delete self.class.delete(@attrs[:id]) if @attrs[:id] end |
#find(id) ⇒ Object
92 93 94 |
# File 'lib/dynomite/item.rb', line 92 def find(id) self.class.find(id) end |
#partition_key ⇒ Object
104 105 106 |
# File 'lib/dynomite/item.rb', line 104 def partition_key self.class.partition_key end |
#replace(hash = {}) ⇒ Object
The method is named replace to clearly indicate that the item is fully replaced.
72 73 74 75 76 77 78 79 80 81 82 83 84 |
# File 'lib/dynomite/item.rb', line 72 def replace(hash={}) @attrs = @attrs.deep_merge(hash) # valid? method comes from ActiveModel::Validations if respond_to? :valid? return false unless valid? end attrs = self.class.replace(@attrs) @attrs = attrs # refresh attrs because it now has the id self end |
#replace!(hash = {}) ⇒ Object
Similar to replace, but raises an error on failed validation. Works that way only if ActiveModel::Validations are included
88 89 90 |
# File 'lib/dynomite/item.rb', line 88 def replace!(hash={}) raise ValidationError, "Validation failed: #{errors..join(', ')}" unless replace(hash) end |
#table_name ⇒ Object
100 101 102 |
# File 'lib/dynomite/item.rb', line 100 def table_name self.class.table_name end |