Class: ActiveCabinet

Inherits:
Object
  • Object
show all
Extended by:
Forwardable
Defined in:
lib/active_cabinet.rb,
lib/active_cabinet/config.rb,
lib/active_cabinet/version.rb,
lib/active_cabinet/metaclass.rb

Overview

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

Defined Under Namespace

Classes: Config

Constant Summary collapse

VERSION =
"0.2.4"

Instance Attribute Summary collapse

Constructor collapse

Attribute Management collapse

Attribute Accessors collapse

Dynamic Attribute Accessors collapse

Loading and Saving collapse

Utilities collapse

Creating Records collapse

Reading Records collapse

Deleting Records collapse

Constructor Details

#initialize(attributes = {}) ⇒ ActiveCabinet

Initializes a new record with #attributes

Parameters:

  • attributes (Hash) (defaults to: {})

    record attributes



16
17
18
# File 'lib/active_cabinet.rb', line 16

def initialize(attributes = {})
  @attributes = default_attributes.merge attributes.transform_keys(&:to_sym)
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(method_name, *args, &blk) ⇒ Object

Provides read/write access to #attributes



90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
# 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

Instance Attribute Details

#attributesHash (readonly)

Returns the attributes of the record.

Returns:

  • (Hash)

    the attributes of the record



8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
# File 'lib/active_cabinet.rb', line 8

class ActiveCabinet
  attr_reader :attributes, :error

  # @!group Constructor

  # Initializes a new record with {attributes}
  #
  # @param [Hash] attributes record attributes
  def initialize(attributes = {})
    @attributes = default_attributes.merge attributes.transform_keys(&:to_sym)
  end

  # @!group Attribute Management

  # Returns an array containing {required_attributes} and {optional_attributes}.
  #
  # @return [Array<Symbol>] array of required attribute keys.
  def allowed_attributes
    self.class.allowed_attributes
  end

  # Returns an array of required record attributes. 
  #
  # @see ActiveCabinet.required_attributes.
  # @return [Array<Symbol>] the array of required attributes
  def required_attributes
    self.class.required_attributes
  end

  # Returns an array of optional record attributes. 
  #
  # @see ActiveCabinet.optional_attributes.
  # @return [Array<Symbol>] the array of optional attributes
  def optional_attributes
    self.class.optional_attributes
  end

  def default_attributes
    self.class.default_attributes
  end

  # Returns +true+ if the object is valid.
  # 
  # @return [Boolean] +true+ if the record is valid.
  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

  # @!group Attribute Accessors

  # Returns the attribute value for the given key.
  #
  # @return [Object] the attribute value.
  def [](key)
    attributes[key]
  end

  # Sets the attribute value for the given key.
  #
  # @param [Symbol] key the attribute key.
  # @param [Object] value the attribute value.
  def []=(key, value)
    attributes[key] = value
  end

  # @!group Dynamic Attribute Accessors

  # Provides read/write access to {attributes}
  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

  # Returns +true+ when calling +#respond_to?+ with an attribute name.
  #
  # @return [Boolean] +true+ if there is a matching attribute.
  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

  # @!group Loading and Saving 

  # 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.
  def reload
    return nil unless saved?
    update cabinet[id]
    self
  end

  # 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.
  def save
    if valid?
      cabinet[id] = attributes
      self
    else
      false
    end
  end

  # 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.
  def saved?
    cabinet.key? id
  end

  # Update the record with new or modified attributes.
  #
  # @param [Hash] new_attributes record attributes
  def update(new_attributes)
    @attributes = attributes.merge(new_attributes.transform_keys &:to_sym)
  end

  # Update the record with new or modified attributes, and save.
  #
  # @param [Hash] new_attributes record attributes
  def update!(new_attributes)
    update new_attributes
    save
  end

  # @!group Utilities

  # Returns a Hash of attributes
  #
  # @return [Hash<Symbol, Object>] the hash of attriibutes/
  def to_h
    attributes
  end

protected

  def cabinet
    self.class.cabinet
  end

end

#errorString? (readonly)

Returns the last validation error, after calling #valid?.

Returns:

  • (String, nil)

    the last validation error, after calling #valid?



8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
# File 'lib/active_cabinet.rb', line 8

class ActiveCabinet
  attr_reader :attributes, :error

  # @!group Constructor

  # Initializes a new record with {attributes}
  #
  # @param [Hash] attributes record attributes
  def initialize(attributes = {})
    @attributes = default_attributes.merge attributes.transform_keys(&:to_sym)
  end

  # @!group Attribute Management

  # Returns an array containing {required_attributes} and {optional_attributes}.
  #
  # @return [Array<Symbol>] array of required attribute keys.
  def allowed_attributes
    self.class.allowed_attributes
  end

  # Returns an array of required record attributes. 
  #
  # @see ActiveCabinet.required_attributes.
  # @return [Array<Symbol>] the array of required attributes
  def required_attributes
    self.class.required_attributes
  end

  # Returns an array of optional record attributes. 
  #
  # @see ActiveCabinet.optional_attributes.
  # @return [Array<Symbol>] the array of optional attributes
  def optional_attributes
    self.class.optional_attributes
  end

  def default_attributes
    self.class.default_attributes
  end

  # Returns +true+ if the object is valid.
  # 
  # @return [Boolean] +true+ if the record is valid.
  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

  # @!group Attribute Accessors

  # Returns the attribute value for the given key.
  #
  # @return [Object] the attribute value.
  def [](key)
    attributes[key]
  end

  # Sets the attribute value for the given key.
  #
  # @param [Symbol] key the attribute key.
  # @param [Object] value the attribute value.
  def []=(key, value)
    attributes[key] = value
  end

  # @!group Dynamic Attribute Accessors

  # Provides read/write access to {attributes}
  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

  # Returns +true+ when calling +#respond_to?+ with an attribute name.
  #
  # @return [Boolean] +true+ if there is a matching attribute.
  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

  # @!group Loading and Saving 

  # 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.
  def reload
    return nil unless saved?
    update cabinet[id]
    self
  end

  # 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.
  def save
    if valid?
      cabinet[id] = attributes
      self
    else
      false
    end
  end

  # 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.
  def saved?
    cabinet.key? id
  end

  # Update the record with new or modified attributes.
  #
  # @param [Hash] new_attributes record attributes
  def update(new_attributes)
    @attributes = attributes.merge(new_attributes.transform_keys &:to_sym)
  end

  # Update the record with new or modified attributes, and save.
  #
  # @param [Hash] new_attributes record attributes
  def update!(new_attributes)
    update new_attributes
    save
  end

  # @!group Utilities

  # Returns a Hash of attributes
  #
  # @return [Hash<Symbol, Object>] the hash of attriibutes/
  def to_h
    attributes
  end

protected

  def cabinet
    self.class.cabinet
  end

end

Class Method Details

.[]=(id, attributes) ⇒ Object

Creates and saves a new record instance.

Parameters:

  • id (String)

    the record id.

  • attributes (Hash)

    the attributes to create.



40
41
42
# File 'lib/active_cabinet/metaclass.rb', line 40

def []=(id, attributes)
  create attributes.merge(id: id)
end

.allArray

Returns all records.

Returns:

  • (Array)

    array of all records.



58
59
60
61
62
# File 'lib/active_cabinet/metaclass.rb', line 58

def all
  cabinet.values.map do |attributes|
    new(attributes)
  end
end

.all_attributesArray

Returns all records, as an Array of Hashes.

Returns:

  • (Array)

    array of all records.



67
68
69
# File 'lib/active_cabinet/metaclass.rb', line 67

def all_attributes
  cabinet.values
end

.allowed_attributesArray<Symbol>

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

Returns:

  • (Array<Symbol>)

    array of required attribute keys.



175
176
177
# File 'lib/active_cabinet/metaclass.rb', line 175

def allowed_attributes
  (optional_attributes || []) + required_attributes + default_attributes.keys
end

.cabinetHashCabinet

Returns the HashCabinet instance.

Returns:

  • (HashCabinet)

    the HashCabinet object.



231
232
233
# File 'lib/active_cabinet/metaclass.rb', line 231

def cabinet
  @cabinet ||= HashCabinet.new "#{Config.dir}/#{cabinet_name}"
end

.cabinet_name(new_name = nil) ⇒ String

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

Parameters:

  • name (String)

    the name of the cabinet file.

Returns:

  • (String)

    name the name of the cabinet file.



240
241
242
243
244
245
246
247
# 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) ⇒ Object

Creates and saves a new record instance.

Parameters:

  • attributes (Hash)

    the attributes to create.

Returns:

  • (Object)

    the record.



48
49
50
51
# File 'lib/active_cabinet/metaclass.rb', line 48

def create(attributes)
  record = new attributes
  record.save || record
end

.default_attributes(args = nil) ⇒ Hash<Symbol, Object>

Sets the default record attribute values.

Parameters:

  • **attributes (Hash<Symbol, Object>)

    one or more attribute names and values.

Returns:

  • (Hash<Symbol, Object>)

    the hash of the default attributes.



213
214
215
216
217
218
219
# 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) ⇒ Boolean

Deletes a record matching the id.

Parameters:

  • id (String)

    the record ID.

Returns:

  • (Boolean)

    true on success, false otherwise.



151
152
153
# File 'lib/active_cabinet/metaclass.rb', line 151

def delete(id)
  !!cabinet.delete(id)
end

.delete_ifObject

Deletes a record for which the block returns true.

Examples:

Delete records using a block

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


159
160
161
# File 'lib/active_cabinet/metaclass.rb', line 159

def delete_if
  cabinet.delete_if { |key, _value| yield self[key] }
end

.dropObject

Deletes all records.



164
165
166
# File 'lib/active_cabinet/metaclass.rb', line 164

def drop
  cabinet.clear
end

.each {|record| ... } ⇒ Object

Yields each record to the given block.

Yield Parameters:

  • record (Object)

    all record instances.



118
119
120
121
122
# File 'lib/active_cabinet/metaclass.rb', line 118

def each
  cabinet.each_value do |attributes|
    yield new(attributes)
  end
end

.find(id) ⇒ Object? Also known as: []

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.

Examples:

Retrieve a record by ID

Song.find 1
Song[1]

Retrieve a different attributes

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

Returns:

  • (Object, nil)

    the object if found, or nil.



105
106
107
108
109
110
111
112
# 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

.firstObject

Returns the first record.

Returns:

  • (Object)

    the record.



127
128
129
# File 'lib/active_cabinet/metaclass.rb', line 127

def first
  find keys.first
end

.lastObject

Returns the last record.

Returns:

  • (Object)

    the record.



134
135
136
# File 'lib/active_cabinet/metaclass.rb', line 134

def last
  find keys.last
end

.optional_attributes(*args) ⇒ Array<Symbol>

Sets the optional record attribute names.

Parameters:

  • *attributes (Array<Symbol>)

    one or more attribute names.

Returns:

  • (Array<Symbol>)

    the array of optional attributes.



198
199
200
201
202
203
204
205
206
207
# 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

.randomObject

Returns a random racord.

Returns:

  • (Object)

    the record.



141
142
143
# File 'lib/active_cabinet/metaclass.rb', line 141

def random
  find keys.sample
end

.required_attributes(*args) ⇒ Array<Symbol>

Sets the required record attribute names.

Parameters:

  • *attributes (Array<Symbol>)

    one or more attribute names.

Returns:

  • (Array<Symbol>)

    the array of required attributes.



183
184
185
186
187
188
189
190
191
192
# 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_hObject

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



224
225
226
# 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| ... } ⇒ Array<Object>

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.

Examples:

Search using a Hash query

Song.where artist: "Iron Maiden"

Search using a block

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

Yield Parameters:

  • record (Object)

    all record instances.

Returns:

  • (Array<Object>)

    record all record instances.



83
84
85
86
87
88
89
90
# 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

Instance Method Details

#[](key) ⇒ Object

Returns the attribute value for the given key.

Returns:

  • (Object)

    the attribute value.



75
76
77
# File 'lib/active_cabinet.rb', line 75

def [](key)
  attributes[key]
end

#[]=(key, value) ⇒ Object

Sets the attribute value for the given key.

Parameters:

  • key (Symbol)

    the attribute key.

  • value (Object)

    the attribute value.



83
84
85
# File 'lib/active_cabinet.rb', line 83

def []=(key, value)
  attributes[key] = value
end

#allowed_attributesArray<Symbol>

Returns an array containing required_attributes and optional_attributes.

Returns:

  • (Array<Symbol>)

    array of required attribute keys.



25
26
27
# File 'lib/active_cabinet.rb', line 25

def allowed_attributes
  self.class.allowed_attributes
end

#default_attributesObject



45
46
47
# File 'lib/active_cabinet.rb', line 45

def default_attributes
  self.class.default_attributes
end

#optional_attributesArray<Symbol>

Returns an array of optional record attributes.

Returns:

  • (Array<Symbol>)

    the array of optional attributes

See Also:



41
42
43
# File 'lib/active_cabinet.rb', line 41

def optional_attributes
  self.class.optional_attributes
end

#reloadself?

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

Returns:

  • (self, nil)

    the object or nil if the object is not stored.



129
130
131
132
133
# File 'lib/active_cabinet.rb', line 129

def reload
  return nil unless saved?
  update cabinet[id]
  self
end

#required_attributesArray<Symbol>

Returns an array of required record attributes.

Returns:

  • (Array<Symbol>)

    the array of required attributes

See Also:



33
34
35
# File 'lib/active_cabinet.rb', line 33

def required_attributes
  self.class.required_attributes
end

#respond_to_missing?(method_name, include_private = false) ⇒ Boolean

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

Returns:

  • (Boolean)

    true if there is a matching attribute.



117
118
119
120
121
# 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

#saveself, false

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

Returns:

  • (self, false)

    the record or false on failure.



139
140
141
142
143
144
145
146
# File 'lib/active_cabinet.rb', line 139

def save
  if valid?
    cabinet[id] = attributes
    self
  else
    false
  end
end

#saved?Boolean

Note:

This method only verifies that the ID of the record exists. The attributes of the instance and the stored record may differ.

Returns true if the record exists in the cabinet.

Returns:

  • (Boolean)

    true if the record is saved in the cabinet.



154
155
156
# File 'lib/active_cabinet.rb', line 154

def saved?
  cabinet.key? id
end

#to_hHash<Symbol, Object>

Returns a Hash of attributes

Returns:

  • (Hash<Symbol, Object>)

    the hash of attriibutes/



178
179
180
# File 'lib/active_cabinet.rb', line 178

def to_h
  attributes
end

#update(new_attributes) ⇒ Object

Update the record with new or modified attributes.

Parameters:

  • new_attributes (Hash)

    record attributes



161
162
163
# File 'lib/active_cabinet.rb', line 161

def update(new_attributes)
  @attributes = attributes.merge(new_attributes.transform_keys &:to_sym)
end

#update!(new_attributes) ⇒ Object

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

Parameters:

  • new_attributes (Hash)

    record attributes



168
169
170
171
# File 'lib/active_cabinet.rb', line 168

def update!(new_attributes)
  update new_attributes
  save
end

#valid?Boolean

Returns true if the object is valid.

Returns:

  • (Boolean)

    true if the record is valid.



52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
# 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