class ActiveCabinet

@!attribute [r] attributes

@return [Hash] the attributes of the record

@!attribute [r] error

@return [String, nil] the last validation error, after calling {valid?}

{ActiveCabinet} lets you create a HashCabinet collection model by subclassing {ActiveCabinet}:

class Song < ActiveCabinet
end

Now, you can perform CRUD operations on this collection, which will be persisted to disk:

# Create
Song.create id: 1, title: 'Moonchild', artist: 'Iron Maiden'

# Read
moonchild = Song[1] # or Song.find 1

# Update
moonchild.title = "22 Acacia Avenue"
moonchild.save
# or
moonchild.update! title: "22 Acacia Avenue"

# Delete
Song.delete 1

Constants

VERSION

Attributes

attributes[R]
error[R]

Public Class Methods

[](id)
Alias for: find
[]=(id, attributes) click to toggle source

Creates and saves a new record instance.

@param [String] id the record id. @param [Hash] attributes the attributes to create.

# File lib/active_cabinet/metaclass.rb, line 40
def []=(id, attributes)
  create attributes.merge(id: id)
end
all() click to toggle source

Returns all records.

@return [Array] array of all records.

# File lib/active_cabinet/metaclass.rb, line 58
def all
  cabinet.values.map do |attributes|
    new(attributes)
  end
end
all_attributes() click to toggle source

Returns all records, as an Array of Hashes.

@return [Array] array of all records.

# File lib/active_cabinet/metaclass.rb, line 67
def all_attributes
  cabinet.values
end
allowed_attributes() click to toggle source

Returns an array containing the keys of all allowed attributes as defined by {required_attributes}, {optional_attributes} and {default_attributes}.

@return [Array<Symbol>] array of required attribute keys.

# File lib/active_cabinet/metaclass.rb, line 175
def allowed_attributes
  (optional_attributes || []) + required_attributes + default_attributes.keys
end
cabinet() click to toggle source

Returns the HashCabinet instance.

@return [HashCabinet] the HashCabinet object.

# File lib/active_cabinet/metaclass.rb, line 231
def cabinet
  @cabinet ||= HashCabinet.new "#{Config.dir}/#{cabinet_name}"
end
cabinet_name(new_name = nil) click to toggle source

Returns or sets the cabinet name. Defaults to the name of the class, lowercase.

@param [String] name the name of the cabinet file. @return [String] name the name of the cabinet file.

# File lib/active_cabinet/metaclass.rb, line 240
def cabinet_name(new_name = nil)
  if new_name
    @cabinet = nil
    @cabinet_name = new_name
  else
    @cabinet_name ||= self.to_s.downcase.gsub('::', '_')
  end
end
create(attributes) click to toggle source

Creates and saves a new record instance.

@param [Hash] attributes the attributes to create. @return [Object] the record.

# File lib/active_cabinet/metaclass.rb, line 48
def create(attributes)
  record = new attributes
  record.save || record
end
default_attributes(args = nil) click to toggle source

Sets the default record attribute values.

@param [Hash<Symbol, Object>] **attributes one or more attribute names and values. @return [Hash<Symbol, Object>] the hash of the default attributes.

# File lib/active_cabinet/metaclass.rb, line 213
def default_attributes(args = nil)
  if args
    @default_attributes = args
  else
    @default_attributes ||= {}
  end
end
delete(id) click to toggle source

Deletes a record matching the id.

@param [String] id the record ID. @return [Boolean] true on success, false otherwise.

# File lib/active_cabinet/metaclass.rb, line 151
def delete(id)
  !!cabinet.delete(id)
end
delete_if() { |self| ... } click to toggle source

Deletes a record for which the block returns true.

@example Delete records using a block

Song.delete_if { |record| record[:artist] == "Iron Maiden" }
# File lib/active_cabinet/metaclass.rb, line 159
def delete_if
  cabinet.delete_if { |key, _value| yield self[key] }
end
drop() click to toggle source

Deletes all records.

# File lib/active_cabinet/metaclass.rb, line 164
def drop
  cabinet.clear
end
each() { |new(attributes)| ... } click to toggle source

Yields each record to the given block.

@yieldparam [Object] record all record instances.

# File lib/active_cabinet/metaclass.rb, line 118
def each
  cabinet.each_value do |attributes|
    yield new(attributes)
  end
end
find(id) click to toggle source

Returns the record matching the id. When providing a Hash with a single key-value pair, it will return the first matching object from the respective {where} query.

@example Retrieve a record by ID

Song.find 1
Song[1]

@example Retrieve a different attributes

Song.find artist: "Iron Maiden"
Song[artist: "Iron Maiden"]

@return [Object, nil] the object if found, or nil.

# File lib/active_cabinet/metaclass.rb, line 105
def find(id)
  if id.is_a? Hash
    where(id).first
  else
    attributes = cabinet[id]
    attributes ? new(attributes) : nil
  end
end
Also aliased as: []
first() click to toggle source

Returns the first record.

@return [Object] the record.

# File lib/active_cabinet/metaclass.rb, line 127
def first
  find keys.first
end
last() click to toggle source

Returns the last record.

@return [Object] the record.

# File lib/active_cabinet/metaclass.rb, line 134
def last
  find keys.last
end
new(attributes = {}) click to toggle source

Initializes a new record with {attributes}

@param [Hash] attributes record attributes

# File lib/active_cabinet.rb, line 16
def initialize(attributes = {})
  @attributes = default_attributes.merge attributes.transform_keys(&:to_sym)
end
optional_attributes(*args) click to toggle source

Sets the optional record attribute names.

@param [Array<Symbol>] *attributes one or more attribute names. @return [Array<Symbol>] the array of optional attributes.

# File lib/active_cabinet/metaclass.rb, line 198
def optional_attributes(*args)
  args = args.first if args.first.is_a? Array
  if args.first === false
    @optional_attributes = false
  elsif args.any?
    @optional_attributes = *args
  else
    @optional_attributes.nil? ? [] : @optional_attributes
  end
end
random() click to toggle source

Returns a random racord.

@return [Object] the record.

# File lib/active_cabinet/metaclass.rb, line 141
def random
  find keys.sample
end
required_attributes(*args) click to toggle source

Sets the required record attribute names.

@param [Array<Symbol>] *attributes one or more attribute names. @return [Array<Symbol>] the array of required attributes.

# File lib/active_cabinet/metaclass.rb, line 183
def required_attributes(*args)
  args = args.first if args.first.is_a? Array
  if args.any?
    @required_attributes = args
    @required_attributes.push :id unless @required_attributes.include? :id
    @required_attributes
  else
    @required_attributes ||= [:id]
  end
end
to_h() click to toggle source

Returns all records as a hash, with record IDs as the keys.

# File lib/active_cabinet/metaclass.rb, line 224
def to_h
  cabinet.to_h.map { |id, attributes| [id, new(attributes)] }.to_h
end
where(query = nil) { |record| ... } click to toggle source

Returns an array of records for which the block returns true. When query is provided, it should be a Hash with a single key and value. The result will be records that have a matching attribute.

@example Search using a Hash query

Song.where artist: "Iron Maiden"

@example Search using a block

Song.where { |record| record[:artist] == "Iron Maiden" }

@yieldparam [Object] record all record instances. @return [Array<Object>] record all record instances.

# File lib/active_cabinet/metaclass.rb, line 83
def where(query = nil)
  if query
    key, value = query.first
    all.select { |record| record[key] == value }
  else
    all.select { |record| yield record }
  end
end

Public Instance Methods

[](key) click to toggle source

Returns the attribute value for the given key.

@return [Object] the attribute value.

# File lib/active_cabinet.rb, line 75
def [](key)
  attributes[key]
end
[]=(key, value) click to toggle source

Sets the attribute value for the given key.

@param [Symbol] key the attribute key. @param [Object] value the attribute value.

# File lib/active_cabinet.rb, line 83
def []=(key, value)
  attributes[key] = value
end
allowed_attributes() click to toggle source

Returns an array containing {required_attributes} and {optional_attributes}.

@return [Array<Symbol>] array of required attribute keys.

# File lib/active_cabinet.rb, line 25
def allowed_attributes
  self.class.allowed_attributes
end
default_attributes() click to toggle source
# File lib/active_cabinet.rb, line 45
def default_attributes
  self.class.default_attributes
end
method_missing(method_name, *args, &blk) click to toggle source

Provides read/write access to {attributes}

Calls superclass method
# File lib/active_cabinet.rb, line 90
def method_missing(method_name, *args, &blk)
  name = method_name
  return attributes[name] if attributes.has_key? name

  suffix = nil

  if name.to_s.end_with?('=', '?')
    suffix = name[-1]
    name = name[0..-2].to_sym
  end

  case suffix
  when "="
    attributes[name] = args.first

  when "?"
    !!attributes[name]

  else
    super

  end
end
optional_attributes() click to toggle source

Returns an array of optional record attributes.

@see ActiveCabinet.optional_attributes. @return [Array<Symbol>] the array of optional attributes

# File lib/active_cabinet.rb, line 41
def optional_attributes
  self.class.optional_attributes
end
reload() click to toggle source

Reads the attributes of the record from the cabinet and returns the record itself. If the record is not stored on disk, returns nil.

@return [self, nil] the object or nil if the object is not stored.

# File lib/active_cabinet.rb, line 129
def reload
  return nil unless saved?
  update cabinet[id]
  self
end
required_attributes() click to toggle source

Returns an array of required record attributes.

@see ActiveCabinet.required_attributes. @return [Array<Symbol>] the array of required attributes

# File lib/active_cabinet.rb, line 33
def required_attributes
  self.class.required_attributes
end
respond_to_missing?(method_name, include_private = false) click to toggle source

Returns true when calling #respond_to? with an attribute name.

@return [Boolean] true if there is a matching attribute.

Calls superclass method
# File lib/active_cabinet.rb, line 117
def respond_to_missing?(method_name, include_private = false)
  name = method_name
  name = name[0..-2].to_sym if name.to_s.end_with?('=', '?')
  attributes.has_key?(name) || super
end
save() click to toggle source

Saves the record to the cabinet if it is valid. Returns the record on success, or false on failure.

@return [self, false] the record or false on failure.

# File lib/active_cabinet.rb, line 139
def save
  if valid?
    cabinet[id] = attributes
    self
  else
    false
  end
end
saved?() click to toggle source

Returns true if the record exists in the cabinet.

@note This method only verifies that the ID of the record exists. The

attributes of the instance and the stored record may differ.

@return [Boolean] true if the record is saved in the cabinet.

# File lib/active_cabinet.rb, line 154
def saved?
  cabinet.key? id
end
to_h() click to toggle source

Returns a Hash of attributes

@return [Hash<Symbol, Object>] the hash of attriibutes/

# File lib/active_cabinet.rb, line 178
def to_h
  attributes
end
update(new_attributes) click to toggle source

Update the record with new or modified attributes.

@param [Hash] new_attributes record attributes

# File lib/active_cabinet.rb, line 161
def update(new_attributes)
  @attributes = attributes.merge(new_attributes.transform_keys &:to_sym)
end
update!(new_attributes) click to toggle source

Update the record with new or modified attributes, and save.

@param [Hash] new_attributes record attributes

# File lib/active_cabinet.rb, line 168
def update!(new_attributes)
  update new_attributes
  save
end
valid?() click to toggle source

Returns true if the object is valid.

@return [Boolean] true if the record is valid.

# File lib/active_cabinet.rb, line 52
def valid?
  missing_keys = required_attributes - attributes.keys
  if missing_keys.any?
    @error = "missing required attributes: #{missing_keys}"
    return false
  end

  if !optional_attributes or optional_attributes.any?
    invalid_keys = attributes.keys - allowed_attributes
    if invalid_keys.any?
      @error = "invalid attributes: #{invalid_keys}"
      return false
    end
  end

  true
end

Protected Instance Methods

cabinet() click to toggle source
# File lib/active_cabinet.rb, line 184
def cabinet
  self.class.cabinet
end