Class: Yarii::ContentModel
- Inherits:
-
Object
- Object
- Yarii::ContentModel
- 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
-
#content ⇒ Object
Returns the value of attribute content.
-
#file_path ⇒ Object
Returns the value of attribute file_path.
Class Method Summary collapse
- .absolute_path(path) ⇒ Object
- .all(sorted: true, order_by: :posted_datetime, order_direction: :desc, subfolder: nil) ⇒ Object
- .find(file_path) ⇒ Object
- .sanitize_filepath(path) ⇒ Object
Instance Method Summary collapse
- #as_json(options = {}) ⇒ Object
- #assign_attributes(new_attributes) ⇒ Object
- #attributes ⇒ Object
- #destroy ⇒ Object
- #file_name ⇒ Object
- #file_stat ⇒ Object
- #generate_file_output ⇒ Object
- #generate_new_file_path ⇒ Object
- #id ⇒ Object
- #load_file_from_path ⇒ Object
- #new_record? ⇒ Boolean
- #persisted? ⇒ Boolean
- #posted_datetime ⇒ Object
- #relative_path ⇒ Object
- #save ⇒ Object
- #update_variables(hash) ⇒ Object
- #variable_names ⇒ Object
- #will_be_published? ⇒ Boolean
Methods included from VariableDefinitions
Methods included from FilePathDefinitions
Methods included from Serializers::YAML
Instance Attribute Details
#content ⇒ Object
Returns the value of attribute content.
22 23 24 |
# File 'app/models/yarii/content_model.rb', line 22 def content @content end |
#file_path ⇒ Object
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(={}) [:except] = ([:except] || []) + ["variable_names"] super() 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 |
#attributes ⇒ Object
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 |
#destroy ⇒ Object
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_name ⇒ Object
149 150 151 |
# File 'app/models/yarii/content_model.rb', line 149 def file_name file_path&.split('/')&.last end |
#file_stat ⇒ Object
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_output ⇒ Object
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_path ⇒ Object
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 |
#id ⇒ Object
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_path ⇒ Object
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
145 146 147 |
# File 'app/models/yarii/content_model.rb', line 145 def new_record? !persisted? end |
#persisted? ⇒ Boolean
142 143 144 |
# File 'app/models/yarii/content_model.rb', line 142 def persisted? file_path.present? && File.exist?(file_path) end |
#posted_datetime ⇒ Object
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_path ⇒ Object
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 |
#save ⇒ Object
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_names ⇒ Object
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
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 |