Class: Yarii::ContentModel

Inherits:
Object
  • Object
show all
Extended by:
ActiveModel::Callbacks, FilePathDefinitions, VariableDefinitions
Includes:
ActiveModel::Model, Serializers::YAML
Defined in:
app/models/yarii/content_model.rb

Constant Summary collapse

YAML_FRONT_MATTER_REGEXP =

code snippet from Jekyll

%r!\A(---\s*\n.*?\n?)^((---|\.\.\.)\s*$\n?)!m
CHECK_BASE_64_REGEXP =
/^([A-Za-z0-9+\/]{4})*([A-Za-z0-9+\/]{4}|[A-Za-z0-9+\/]{3}=|[A-Za-z0-9+\/]{2}==)$/
PAGINATION_SIZE =
12

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from VariableDefinitions

variables

Methods included from FilePathDefinitions

folder

Methods included from Serializers::YAML

#as_yaml, #from_yaml

Instance Attribute Details

#contentObject

Returns the value of attribute content.



22
23
24
# File 'app/models/yarii/content_model.rb', line 22

def content
  @content
end

#file_pathObject

Returns the value of attribute file_path.



22
23
24
# File 'app/models/yarii/content_model.rb', line 22

def file_path
  @file_path
end

Class Method Details

.absolute_path(path) ⇒ Object



44
45
46
# File 'app/models/yarii/content_model.rb', line 44

def self.absolute_path(path)
  sanitize_filepath File.join(self.base_path, self.folder_path, path)
end

.all(sorted: true, order_by: :posted_datetime, order_direction: :desc, subfolder: nil) ⇒ Object



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
# File 'app/models/yarii/content_model.rb', line 57

def self.all(sorted: true, order_by: :posted_datetime, order_direction: :desc, subfolder: nil)
  raise "Missing base path for the #{self.name} content model" if self.base_path.blank?

  if self.folder_path.present?
    # find all content files in the folder and any direct subfolders
    glob_pattern = File.join(self.base_path, self.folder_path, subfolder.to_s, "**/*.{md,markdown,html}")

    files = Dir.glob(glob_pattern)
  else
    # find any content files not in special underscore folders
    files = Rake::FileList.new(
      File.join(self.base_path, "**/*.md"),
      File.join(self.base_path, "**/*.markdown"),
      File.join(self.base_path, "**/*.html")
    ) do |fl|
      basename = Pathname.new(self.base_path).basename.sub(/^_/, '')
      fl.exclude(/#{basename}\/\_/)
    end
  end

  models = files.map do |file_path|
    unless File.directory? file_path
      new(file_path: file_path).tap do |content_model|
        content_model.load_file_from_path
      end
    end
  end.compact

  if sorted
    begin
      models.sort_by! do |content_model|
        content_model.send(order_by)
      end
    rescue ArgumentError => e
      Rails.logger.warn ("Sorting #{self.name} by #{order_by}, value comparison failed")
      models.sort_by!(&:posted_datetime)
    end
    order_direction == :desc ? models.reverse : models
  else
    models
  end
end

.find(file_path) ⇒ Object



29
30
31
32
33
34
35
36
37
38
39
40
41
42
# File 'app/models/yarii/content_model.rb', line 29

def self.find(file_path)
  raise "Missing base path for the #{self.name} content model" if self.base_path.blank?

  # decode Base64 path if necessary
  if CHECK_BASE_64_REGEXP.match file_path
    file_path = Base64::decode64 file_path
  end

  file_path = absolute_path(file_path)

  new(file_path: file_path).tap do |content_model|
    content_model.load_file_from_path
  end
end

.sanitize_filepath(path) ⇒ Object



48
49
50
51
52
53
54
55
# File 'app/models/yarii/content_model.rb', line 48

def self.sanitize_filepath(path)
  if path.include? "../"
    # TODO: output better error message ;)
    raise "Nice try, Hacker Dude. You lose."
  end

  path
end

Instance Method Details

#as_json(options = {}) ⇒ Object



265
266
267
268
# File 'app/models/yarii/content_model.rb', line 265

def as_json(options={})
  options[:except] = (options[:except] || []) + ["variable_names"]
  super(options)
end

#assign_attributes(new_attributes) ⇒ Object



211
212
213
214
215
216
217
218
219
220
221
222
# File 'app/models/yarii/content_model.rb', line 211

def assign_attributes(new_attributes)
  # Changed from Active Model
  # (we implement our own method of assigning attributes)

  if !new_attributes.respond_to?(:stringify_keys)
    raise ArgumentError, "When assigning attributes, you must pass a hash as an argument."
  end
  return if new_attributes.empty?

  attributes = new_attributes.stringify_keys
  update_variables(sanitize_for_mass_assignment(attributes))
end

#attributesObject



203
204
205
206
207
208
209
# File 'app/models/yarii/content_model.rb', line 203

def attributes
  ret = {}
  variable_names.each do |var_name|
    ret[var_name.to_s] = send(var_name.to_sym)
  end
  ret
end

#destroyObject



120
121
122
123
124
125
126
127
128
129
# File 'app/models/yarii/content_model.rb', line 120

def destroy
  run_callbacks :destroy do
    if persisted?
      File.delete(file_path)
      self.file_path = nil

      true
    end
  end
end

#file_nameObject



149
150
151
# File 'app/models/yarii/content_model.rb', line 149

def file_name
  file_path&.split('/')&.last
end

#file_statObject



153
154
155
156
157
# File 'app/models/yarii/content_model.rb', line 153

def file_stat
  if persisted?
    @file_stat ||= File.stat(file_path)
  end
end

#generate_file_outputObject



193
194
195
196
197
# File 'app/models/yarii/content_model.rb', line 193

def generate_file_output
  # NOTE: this can totally be subclassed by various content models…

  as_yaml + "---\n\n" + content.to_s
end

#generate_new_file_pathObject



175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
# File 'app/models/yarii/content_model.rb', line 175

def generate_new_file_path
  # NOTE: this can totally be subclassed by various content models…

  if use_date = date && date.to_datetime
    date_prefix = "#{use_date.strftime('%Y-%m-%d')}-"
  else
    date_prefix = "#{Date.current.strftime('%Y-%m-%d')}-"
  end

  slug = if title
    title.gsub(/['|"]/,'').parameterize
  else
    "untitled_#{self.class.name.parameterize}"
  end

  self.class.absolute_path("#{date_prefix}#{slug}.md")
end

#idObject



131
132
133
134
135
# File 'app/models/yarii/content_model.rb', line 131

def id
  if persisted?
    Base64.encode64(relative_path).strip
  end
end

#load_file_from_pathObject



245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
# File 'app/models/yarii/content_model.rb', line 245

def load_file_from_path
  file_data = File.read(file_path)

  loaded_variables = {}

  begin
    if file_data =~ YAML_FRONT_MATTER_REGEXP
      self.content = $'
      loaded_variables = ::SafeYAML.load(Regexp.last_match(1))
    end
  rescue SyntaxError => e
    Rails.logger.error "Error: YAML Exception reading #{file_path}: #{e.message}"
  end

  if loaded_variables.present?
    update_variables(loaded_variables)
  end
end

#new_record?Boolean

Returns:

  • (Boolean)


145
146
147
# File 'app/models/yarii/content_model.rb', line 145

def new_record?
  !persisted?
end

#persisted?Boolean

Returns:

  • (Boolean)


142
143
144
# File 'app/models/yarii/content_model.rb', line 142

def persisted?
  file_path.present? && File.exist?(file_path)
end

#posted_datetimeObject



159
160
161
162
163
164
165
166
167
# File 'app/models/yarii/content_model.rb', line 159

def posted_datetime
  if variable_names.include?(:date) && date
    date.to_datetime
  elsif matched = file_name.to_s.match(/^[0-9]+-[0-9]+-[0-9]+/)
    matched[0].to_datetime
  elsif persisted?
    file_stat.mtime
  end
end

#relative_pathObject



137
138
139
140
# File 'app/models/yarii/content_model.rb', line 137

def relative_path
  relative_base = File.join(self.class.base_path, self.class.folder_path)
  file_path.sub(/^#{relative_base}/, '')
end

#saveObject



100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
# File 'app/models/yarii/content_model.rb', line 100

def save
  run_callbacks :save do
    if file_path.blank?
      self.file_path = generate_new_file_path
    end

    # Create folders if necessary
    dir = File.dirname(file_path)
    unless File.directory?(dir)
      FileUtils.mkdir_p(dir)
    end

    File.open(file_path, 'w') do |f|
      f.write(generate_file_output)
    end

    true
  end
end

#update_variables(hash) ⇒ Object



224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
# File 'app/models/yarii/content_model.rb', line 224

def update_variables(hash)
  hash.each do |key, value|
    begin
      send("#{key}=", value)
    rescue NoMethodError
      Rails.logger.warn (":#{key}: is not a defined front matter variable, will be available as read-only")

      # If an unfamiliar variable is present, allow it to be set as a
      # read-only value for future serialization, but it still can't be set
      # via an accessor writer. Kind ugly code, but it works
      instance_variable_set("@#{key}", value)
      unless key.to_sym.in? variable_names
        variable_names << key.to_sym
        define_singleton_method key.to_sym do
          instance_variable_get("@#{key}")
        end
      end
    end
  end
end

#variable_namesObject



199
200
201
# File 'app/models/yarii/content_model.rb', line 199

def variable_names
  @variable_names ||= self.class.variable_names&.dup || []
end

#will_be_published?Boolean

Returns:

  • (Boolean)


169
170
171
172
173
# File 'app/models/yarii/content_model.rb', line 169

def will_be_published?
  return false if respond_to?(:published) and published === false
  return false if respond_to?(:draft) and draft
  true
end