Class: TmsuModel

Inherits:
Object
  • Object
show all
Includes:
TmsuFileAPI
Defined in:
lib/tmsu_file_db.rb

Constant Summary collapse

Config =
{ root_path: "." }
Validations =
Hash.new { |h,k| h[k] = [] }
Callbacks =
{}

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from TmsuFileAPI

#build_tag_arg, #files, #merge_tag, #require_persisted, #tag, #tag_selector, #tags, #untag, #untag_selector

Constructor Details

#initialize(attrs = {}, &blk) ⇒ TmsuModel

Returns a new instance of TmsuModel.



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

def initialize(attrs={}, &blk)
  attrs = attrs.with_indifferent_access
  # normally for re-initializing a record from a file, .from_file is used.
  # but there is another way, which is to pass a block to initialize
  # which returns a path string.
  # Example: TmsuModel.new { "file.txt" }.path # => "file.txt"
  @path = blk ? blk.call : build_path
  @attributes = attrs
  @persisted = File.exists? @path
  @errors = []
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(sym, *arguments, &blk) ⇒ Object

Forwards method missing to getter, if possible To respect indifferent access of attributes, uses has_key? instead of keys.include?



150
151
152
153
154
155
156
157
# File 'lib/tmsu_file_db.rb', line 150

def method_missing(sym, *arguments, &blk)
  super unless defined?(attributes) && attributes.is_a?(Hash)
  if attributes.has_key? sym
    attributes[sym]
  else
    super
  end
end

Instance Attribute Details

#attributesObject (readonly)

Returns the value of attribute attributes.



112
113
114
# File 'lib/tmsu_file_db.rb', line 112

def attributes
  @attributes
end

#errorsObject (readonly)

Returns the value of attribute errors.



112
113
114
# File 'lib/tmsu_file_db.rb', line 112

def errors
  @errors
end

#pathObject (readonly)

Returns the value of attribute path.



112
113
114
# File 'lib/tmsu_file_db.rb', line 112

def path
  @path
end

Class Method Details

.add_callback(name, &blk) ⇒ Object



46
47
48
# File 'lib/tmsu_file_db.rb', line 46

def self.add_callback name, &blk
  Callbacks[name] = blk
end

.allObject



88
89
90
# File 'lib/tmsu_file_db.rb', line 88

def self.all
  Dir.glob(query_glob).map &method(:from_file)
end

.configure(root_path:) ⇒ Object



31
32
33
34
35
# File 'lib/tmsu_file_db.rb', line 31

def self.configure(root_path:)
  Config[:root_path] = root_path || "./db".tap do |path|
    `mkdir -p #{path}`
  end
end

.create(attrs) ⇒ Object



61
62
63
# File 'lib/tmsu_file_db.rb', line 61

def self.create(attrs)
  new(attrs).tap(&:save)
end

.destroy_all(opts = {}) ⇒ Object



108
109
110
# File 'lib/tmsu_file_db.rb', line 108

def self.destroy_all opts={}
  Dir.glob(query_glob).tap { |list| list.each { |path| `rm #{path}` } }
end

.escape_hash_whitespace(hash) ⇒ Object



77
78
79
80
81
82
# File 'lib/tmsu_file_db.rb', line 77

def self.escape_hash_whitespace(hash)
  hash.reduce({}) do |result, (k,v)|
    result[escape_whitespace(k)] = escape_whitespace v
    result
  end
end

.escape_whitespace(string) ⇒ Object



73
74
75
# File 'lib/tmsu_file_db.rb', line 73

def self.escape_whitespace(string)
  string.to_s.gsub(/(?<!\\)\s/, '\ ')
end

.find_by(opts) ⇒ Object



65
66
67
# File 'lib/tmsu_file_db.rb', line 65

def self.find_by opts
  where(opts).first
end

.from_file(path) ⇒ Object



84
85
86
# File 'lib/tmsu_file_db.rb', line 84

def self.from_file(path)
  new(escape_hash_whitespace TmsuRuby.file(path).tags) { path }
end

.generate_path(within: '.') ⇒ Object



24
25
26
27
28
29
# File 'lib/tmsu_file_db.rb', line 24

def self.generate_path(within: '.')
  loop do
    path = "#{within}/#{SecureRandom.hex}"
    break path unless File.exists?(path)
  end
end

.opts_to_query(opts) ⇒ Object



50
51
52
53
54
55
56
57
58
59
# File 'lib/tmsu_file_db.rb', line 50

def self.opts_to_query opts
  case opts
  when Array
    opts.map { |opt| escape_whitespace opt }.join " "
  when Hash
    opts.map do |k,v|
      "#{escape_whitespace k}=#{escape_whitespace v}"
    end.join(" ")
  end
end

.query(string) ⇒ Object



92
93
94
# File 'lib/tmsu_file_db.rb', line 92

def self.query string
  TmsuRuby.file(query_glob).files(string).map &method(:from_file)
end

.query_globObject



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

def self.query_glob
  "#{root_path}/*"
end

.root_pathObject



20
21
22
# File 'lib/tmsu_file_db.rb', line 20

def self.root_path
  Config[:root_path] || generate_path(within: "./db")
end

.update_all(opts = {}) ⇒ Object



96
97
98
99
100
101
102
103
104
105
106
# File 'lib/tmsu_file_db.rb', line 96

def self.update_all opts={}
  Dir.glob(query_glob).each do |path|
    errors = from_file(path).tap { |inst| inst.update(opts) }.errors
    unless errors.empty?
      raise(
        ArgumentError, "couldn't update all. Path #{path} caused errors: #{errors.join(", ")}"
      )
    end
  end
  true
end

.validate(attribute = :generic, &blk) ⇒ Object



38
39
40
41
42
43
44
# File 'lib/tmsu_file_db.rb', line 38

def self.validate(attribute=:generic, &blk)
  if attribute == :generic
    Validations[:generic] << blk
  else
    Validations[attribute] << blk
  end
end

.where(opts = {}) ⇒ Object



69
70
71
# File 'lib/tmsu_file_db.rb', line 69

def self.where opts={}
  query opts_to_query opts
end

Instance Method Details

#[](k) ⇒ Object



143
144
145
# File 'lib/tmsu_file_db.rb', line 143

def [](k)
  attributes[k]
end

#[]=(k, v) ⇒ Object



139
140
141
# File 'lib/tmsu_file_db.rb', line 139

def []=(k,v)
  attributes[k] = v
end

#append(text) ⇒ Object



165
166
167
168
# File 'lib/tmsu_file_db.rb', line 165

def append(text)
  write(text, append: true)
  self
end

#build_pathObject



133
134
135
136
137
# File 'lib/tmsu_file_db.rb', line 133

def build_path
  self.class.generate_path(
    within: self.class::Config[:root_path] || "."
  )
end

#delete(attr) ⇒ Object



232
233
234
235
236
237
238
239
240
# File 'lib/tmsu_file_db.rb', line 232

def delete(attr)
  if attributes[attr].nil?
    untag(attr)
  else
    val = attributes[attr]
    untag("#{attr}=#{val}")
  end
  attributes.delete attr
end

#destroyObject



225
226
227
228
229
230
# File 'lib/tmsu_file_db.rb', line 225

def destroy
  `tmsu-fs-rm #{path}`
  `rm #{path}`
  @persisted = false
  self
end

#ensure_persistedObject



192
193
194
# File 'lib/tmsu_file_db.rb', line 192

def ensure_persisted
  `touch #{path}` unless persisted?
end

#ensure_root_pathObject



126
127
128
129
130
131
# File 'lib/tmsu_file_db.rb', line 126

def ensure_root_path
  unless @root_dir_created
    `mkdir -p #{self.class.root_path}`
    @root_dir_created = true
  end
end

#persisted?Boolean

Returns:

  • (Boolean)


188
189
190
# File 'lib/tmsu_file_db.rb', line 188

def persisted?
  !!@persisted
end

#saveObject



196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
# File 'lib/tmsu_file_db.rb', line 196

def save
  ensure_persisted
  ensure_root_path
  original_attributes = tags
  attributes.each do |k,v|
    if !v.nil? && !(original_attributes[k] == v)
      untag "#{k}=#{original_attributes[k]}"
    end
  end
  return false unless valid?
  tag attributes
  @persisted = true
  @attributes = tags.with_indifferent_access
  true
end

#update(attrs = {}) ⇒ Object



212
213
214
215
216
217
218
219
220
221
222
223
# File 'lib/tmsu_file_db.rb', line 212

def update attrs={}
  original_attrs = attributes.clone
  attrs.each_key { |k| self[k] = attrs[k] }
  unless valid?
    # rollback attribute change
    self.attributes.clear
    original_attrs.each { |k,v| self[k] = v }
    return false
  end
  save
  true
end

#valid?Boolean

Returns:

  • (Boolean)


170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
# File 'lib/tmsu_file_db.rb', line 170

def valid?
  @errors = []
  Validations.each do |type, procs|
    procs.each do |proc|
      some_errors = if type.eql?(:generic)
         proc.call self
      else
        proc.call self[type], self
      end
      raise(
        ArgumentError, "validations must return arrays"
      ) unless some_errors.is_a?(Array)
      @errors.concat some_errors
    end
  end
  @errors.empty?
end

#write(text, append: false) ⇒ Object



159
160
161
162
163
# File 'lib/tmsu_file_db.rb', line 159

def write(text, append: false)
  return unless text
  File.open(path, "w#{"a" if append}") { |f| f.write text }
  self
end