Class: Dynamini::Base

Inherits:
Object
  • Object
show all
Includes:
ActiveModel::Validations
Defined in:
lib/dynamini/base.rb

Overview

Core db interface class.

Constant Summary collapse

BATCH_SIZE =
25
GETTER_PROCS =
{
    integer:  proc { |v| v.to_i },
    datetime: proc { |v| Time.at(v.to_f) },
    float:    proc { |v| v.to_f },
    symbol:   proc { |v| v.to_sym },
    string:   proc { |v| v },
    boolean:  proc { |v| [true, 'true', '1', '1.0'].include? v },
    array:    proc { |v| v ? (v.is_a?(String) ? JSON.parse(v) : v) : [] }
}
SETTER_PROCS =
{
    datetime: proc { |v| v.to_f },
    array: proc { |v| v if v.is_a? Array }
}

Class Attribute Summary collapse

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(attributes = {}, new_record = true) ⇒ Base

Instance Methods



136
137
138
139
140
141
142
143
144
# File 'lib/dynamini/base.rb', line 136

def initialize(attributes = {}, new_record = true)
  @changed = Set.new
  @new_record = new_record
  @attributes = {}

  attributes.each do |k, v|
    write_attribute(k, v, new_record)
  end
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(name, *args, &block) ⇒ Object (private)



344
345
346
347
348
349
350
351
352
353
354
# File 'lib/dynamini/base.rb', line 344

def method_missing(name, *args, &block)
  if write_method?(name)
    attribute = name[0..-2].to_sym
    new_value = args.first
    write_attribute(attribute, new_value)
  elsif read_method?(name)
    read_attribute(name)
  else
    super
  end
end

Class Attribute Details

.batch_write_queueObject



55
56
57
# File 'lib/dynamini/base.rb', line 55

def batch_write_queue
  @batch_write_queue ||= []
end

.in_memoryObject



51
52
53
# File 'lib/dynamini/base.rb', line 51

def in_memory
  @in_memory || false
end

.range_keyObject (readonly)

Returns the value of attribute range_key.



24
25
26
# File 'lib/dynamini/base.rb', line 24

def range_key
  @range_key
end

Instance Attribute Details

#attributesObject (readonly)

Returns the value of attribute attributes.



5
6
7
# File 'lib/dynamini/base.rb', line 5

def attributes
  @attributes
end

Class Method Details

.batch_find(ids = []) ⇒ Object



103
104
105
106
107
108
109
110
111
112
113
# File 'lib/dynamini/base.rb', line 103

def batch_find(ids = [])
  return [] if ids.length < 1
  objects = []
  fail StandardError, 'Batch is limited to 100 items' if ids.length > 100
  key_structure = ids.map { |i| { hash_key => i.to_s } }
  response = dynamo_batch_get(key_structure)
  response.responses[table_name].each do |item|
    objects << new(item.symbolize_keys, false)
  end
  objects
end

.clientObject



59
60
61
62
63
64
65
66
67
68
69
# File 'lib/dynamini/base.rb', line 59

def client
  if in_memory
    @client ||= Dynamini::TestClient.new(hash_key)
  else
    @client ||= Aws::DynamoDB::Client.new(
        region: Dynamini.configuration.region,
        access_key_id: Dynamini.configuration.access_key_id,
        secret_access_key: Dynamini.configuration.secret_access_key
    )
  end
end

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



71
72
73
74
# File 'lib/dynamini/base.rb', line 71

def create(attributes, options = {})
  model = new(attributes, true)
  model if model.save(options)
end

.create!(attributes, options = {}) ⇒ Object



76
77
78
79
# File 'lib/dynamini/base.rb', line 76

def create!(attributes, options = {})
  model = new(attributes, true)
  model if model.save!(options)
end

.create_key_hash(hash_value, range_value = nil) ⇒ Object



304
305
306
307
308
# File 'lib/dynamini/base.rb', line 304

def self.create_key_hash(hash_value, range_value = nil)
  key_hash = { self.hash_key => hash_value }
  key_hash[self.range_key] = range_value if self.range_key
  key_hash
end

.define_handled_getter(column, format_class, options = {}) ⇒ Object



364
365
366
367
368
369
370
371
372
373
374
# File 'lib/dynamini/base.rb', line 364

def self.define_handled_getter(column, format_class, options = {})
  proc = GETTER_PROCS[format_class]
  fail 'Unsupported data type: ' + format_class.to_s if proc.nil?
  define_method(column) do
    if @attributes.key?(column)
      proc.call(read_attribute(column))
    else
      options[:default] || nil
    end
  end
end

.define_handled_setter(column, format_class) ⇒ Object



376
377
378
379
380
381
382
383
384
385
386
387
388
# File 'lib/dynamini/base.rb', line 376

def self.define_handled_setter(column, format_class)
  setter_symbol = (column.to_s + '=').to_sym
  proc = SETTER_PROCS[format_class]
  if proc
    define_method(setter_symbol) do |value|
      write_attribute(column, proc.call(value))
    end
  else
    define_method(setter_symbol) do |value|
      write_attribute(column, value)
    end
  end
end

.dynamo_batch_get(key_struct) ⇒ Object



277
278
279
280
281
282
283
# File 'lib/dynamini/base.rb', line 277

def self.dynamo_batch_get(key_struct)
  client.batch_get_item(
      request_items: {
          table_name => { keys: key_struct }
      }
  )
end

.dynamo_batch_save(model_array) ⇒ Object



285
286
287
288
289
290
291
292
293
294
295
296
# File 'lib/dynamini/base.rb', line 285

def self.dynamo_batch_save(model_array)
  put_requests = []
  model_array.each do |model|
    put_requests << { put_request: {
        item: model.attributes.reject{ |_k, v| v.blank? }.stringify_keys
    } }
  end
  request_options = { request_items: {
      "#{table_name}" => put_requests }
  }
  client.batch_write_item(request_options)
end

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



115
116
117
118
119
120
121
122
123
124
# File 'lib/dynamini/base.rb', line 115

def enqueue_for_save(attributes, options = {})
  model = new(attributes, true)
  model.generate_timestamps! unless options[:skip_timestamps]
  if model.valid?
    batch_write_queue << model
    flush_queue! if batch_write_queue.length == BATCH_SIZE
    return true
  end
  false
end

.exists?(key) ⇒ Boolean

Returns:

  • (Boolean)


88
89
90
91
# File 'lib/dynamini/base.rb', line 88

def exists?(key)
  r = client.get_item(table_name: table_name, key: { hash_key => key.to_s })
  r.item.present?
end

.find(hash_value, range_value = nil) ⇒ Object



81
82
83
84
85
86
# File 'lib/dynamini/base.rb', line 81

def find(hash_value, range_value = nil)
  fail 'Range key cannot be blank.' if range_key && range_value.nil?
  response = client.get_item(table_name: table_name, key: create_key_hash(hash_value, range_value))
  raise 'Item not found.' unless response.item
  new(response.item.symbolize_keys, false)
end

.find_or_new(key) ⇒ Object



93
94
95
96
97
98
99
100
101
# File 'lib/dynamini/base.rb', line 93

def find_or_new(key)
  fail 'Key cannot be blank.' if (key.nil? || key == '')
  r = client.get_item(table_name: table_name, key: { hash_key => key.to_s })
  if r.item
    new(r.item.symbolize_keys, false)
  else
    new(hash_key => key.to_s)
  end
end

.flush_queue!Object



126
127
128
129
130
# File 'lib/dynamini/base.rb', line 126

def flush_queue!
  response = dynamo_batch_save(batch_write_queue)
  self.batch_write_queue = []
  response
end

.handle(column, format_class, options = {}) ⇒ Object



42
43
44
45
# File 'lib/dynamini/base.rb', line 42

def handle(column, format_class, options = {})
  define_handled_getter(column, format_class, options)
  define_handled_setter(column, format_class)
end

.hash_keyObject



47
48
49
# File 'lib/dynamini/base.rb', line 47

def hash_key
  @hash_key || :id
end

.set_hash_key(key) ⇒ Object



34
35
36
# File 'lib/dynamini/base.rb', line 34

def set_hash_key(key)
  @hash_key = key
end

.set_range_key(key) ⇒ Object



38
39
40
# File 'lib/dynamini/base.rb', line 38

def set_range_key(key)
  @range_key = key
end

.set_table_name(name) ⇒ Object



30
31
32
# File 'lib/dynamini/base.rb', line 30

def set_table_name(name)
  @table_name = name
end

.table_nameObject



26
27
28
# File 'lib/dynamini/base.rb', line 26

def table_name
  @table_name ||= name.demodulize.tableize
end

Instance Method Details

#==(other) ⇒ Object



146
147
148
# File 'lib/dynamini/base.rb', line 146

def ==(other)
  hash_key == other.hash_key if other.is_a?(self.class)
end

#assign_attributes(attributes) ⇒ Object



150
151
152
153
154
155
# File 'lib/dynamini/base.rb', line 150

def assign_attributes(attributes)
  attributes.each do |key, value|
    write_attribute(key, value)
  end
  nil
end

#changedObject



211
212
213
# File 'lib/dynamini/base.rb', line 211

def changed
  @changed.to_a
end

#changesObject



204
205
206
207
208
209
# File 'lib/dynamini/base.rb', line 204

def changes
  @attributes.select { |attribute| @changed.include?(attribute.to_s) &&
      attribute != self.class.hash_key &&
      attribute != self.class.range_key
  }
end

#deleteObject



199
200
201
202
# File 'lib/dynamini/base.rb', line 199

def delete
  delete_from_dynamo
  self
end

#increment!(attribute_increments, opts = {}) ⇒ Object



192
193
194
195
196
197
# File 'lib/dynamini/base.rb', line 192

def increment!(attribute_increments, opts = {})
  attribute_increments.each do |a, v|
    validate_incrementable_attribute(a, v)
  end
  increment_to_dynamo(attribute_increments, opts)
end

#new_record?Boolean

Returns:

  • (Boolean)


215
216
217
# File 'lib/dynamini/base.rb', line 215

def new_record?
  @new_record
end

#save(options = {}) ⇒ Object



167
168
169
# File 'lib/dynamini/base.rb', line 167

def save(options = {})
  @changed.empty? || (valid? && trigger_save(options))
end

#save!(options = {}) ⇒ Object



171
172
173
174
175
176
177
178
179
180
181
# File 'lib/dynamini/base.rb', line 171

def save!(options = {})
  options[:validate] = true if options[:validate].nil?

  unless @changed.empty?
    if (options[:validate] && valid?) || !options[:validate]
      trigger_save(options)
    else
      raise StandardError, errors.full_messages
    end
  end
end

#touch(options = {validate: true}) ⇒ Object

Raises:

  • (RuntimeError)


183
184
185
186
187
188
189
190
# File 'lib/dynamini/base.rb', line 183

def touch(options = {validate: true})
  raise RuntimeError, 'Cannot touch a new record.' if new_record?
  if (options[:validate] && valid?) || !options[:validate]
    trigger_touch
  else
    raise StandardError, errors.full_messages
  end
end

#update_attribute(key, value, options = {}) ⇒ Object



157
158
159
160
# File 'lib/dynamini/base.rb', line 157

def update_attribute(key, value, options = {})
  write_attribute(key, value)
  save!(options)
end

#update_attributes(attributes, options = {}) ⇒ Object



162
163
164
165
# File 'lib/dynamini/base.rb', line 162

def update_attributes(attributes, options = {})
  assign_attributes(attributes)
  save!(options)
end