Skip to content

Commit cdcda15

Browse files
authored
Add support for validation on resource classess (#56)
```ruby class Content::Article < Perron::Resource validates :title, presence: true # or # validates :description, length: { in: 1..158 } delegate :title, :description, to: :metadata ``` Then validate using `bin/rails perron:validate`. This task can be run in pre-commit hook or as part of your `bin/ci` script. Output looks like this: ```bash rails perron:validate FFFFFFFFFF.................... Resource: /Users/e/Tresorit/Code/OSS/perron/site/app/content/resources/faq-component-tailwind.md - Name is too short (minimum is 50 characters) Resource: /Users/e/Tresorit/Code/OSS/perron/site/app/content/resources/indexnow.md - Name is too short (minimum is 50 characters) Resource: /Users/e/Tresorit/Code/OSS/perron/site/app/content/resources/netlify.md - Name is too short (minimum is 50 characters) Resource: /Users/e/Tresorit/Code/OSS/perron/site/app/content/resources/new.md - Name is too short (minimum is 50 characters) ```
1 parent 39cf341 commit cdcda15

File tree

10 files changed

+123
-5
lines changed

10 files changed

+123
-5
lines changed

lib/perron/collection.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ def configuration(resource_class = "Content::#{name.classify}".safe_constantize)
1616
end
1717

1818
def all(resource_class = "Content::#{name.classify}".safe_constantize)
19-
@all ||= Dir.glob("#{@collection_path}/**/*.*").map do |file_path|
19+
Dir.glob("#{@collection_path}/**/*.*").map do |file_path|
2020
resource_class.new(file_path)
2121
end.select(&:published?)
2222
end
@@ -35,5 +35,7 @@ def find_by_file_name(file_name, resource_class = Resource)
3535
Perron.configuration.allowed_extensions.lazy.map { File.join(@collection_path, [file_name, it].join(".")) }.find { File.exist?(it) }
3636
)
3737
end
38+
39+
def validate = Perron::Site::Validate.new(collections: [self]).validate
3840
end
3941
end

lib/perron/engine.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ class Engine < Rails::Engine
77
end
88

99
rake_tasks do
10-
load File.expand_path("../tasks/perron.rake", __FILE__)
10+
load File.expand_path("../tasks/build.rake", __FILE__)
11+
load File.expand_path("../tasks/validate.rake", __FILE__)
1112
end
1213
end
1314
end

lib/perron/resource.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ module Perron
1515
class Resource
1616
ID_LENGTH = 8
1717

18+
include ActiveModel::Validations
19+
1820
include Perron::Resource::Configuration
1921
include Perron::Resource::Core
2022
include Perron::Resource::ClassMethods
@@ -24,6 +26,7 @@ class Resource
2426
attr_reader :file_path, :id
2527

2628
def initialize(file_path)
29+
@errors = ActiveModel::Errors.new(self)
2730
@file_path = file_path
2831
@id = generate_id
2932

lib/perron/site.rb

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,24 @@
11
# frozen_string_literal: true
22

3-
require "perron/site/builder"
43
require "perron/collection"
54
require "perron/data"
65
require "perron/data/proxy"
6+
require "perron/site/builder"
7+
require "perron/site/validate"
78

89
module Perron
910
module Site
1011
module_function
1112

1213
def build = Perron::Site::Builder.new.build
1314

15+
def validate = Perron::Site::Validate.new.validate
16+
1417
def collections
15-
@collections ||= Dir.children(Perron.configuration.input)
18+
Dir.children(Perron.configuration.input)
1619
.select { File.directory?(File.join(Perron.configuration.input, it)) }
1720
.reject { it == "data" }
21+
.reject { it == "themes" } # TODO: remove me
1822
.map { Collection.new(it) }
1923
end
2024

lib/perron/site/validate.rb

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# frozen_string_literal: true
2+
3+
module Perron
4+
module Site
5+
class Validate
6+
def initialize(collections: Perron::Site.collections)
7+
@collections = collections
8+
@failures = []
9+
end
10+
11+
def validate
12+
@collections.each { validate_collection it }
13+
14+
[
15+
puts,
16+
failures_report,
17+
puts
18+
].compact_blank.join
19+
20+
puts [
21+
"Validation finished",
22+
(" with #{@failures.count} failures" if @failures.any?),
23+
"."
24+
].join
25+
end
26+
27+
private
28+
29+
Failure = ::Data.define(:identifier, :errors)
30+
31+
GREEN = "\e[32m"
32+
RED = "\e[31m"
33+
RESET = "\e[0m"
34+
35+
def validate_collection(collection)
36+
collection.resources.each do |resource|
37+
resource.validate ? success : failed(resource)
38+
end
39+
end
40+
41+
def success = print "#{GREEN}.#{RESET}"
42+
43+
def failed(resource)
44+
print "#{RED}F#{RESET}"
45+
46+
errors = []
47+
48+
if resource.respond_to?(:errors) && resource.errors.respond_to?(:full_messages) && resource.errors.any?
49+
errors = resource.errors.full_messages
50+
end
51+
52+
@failures << Failure.new(identifier: resource.file_path, errors: errors)
53+
end
54+
55+
def failures_report
56+
@failures.each do |failure|
57+
puts "Resource: #{failure.identifier}"
58+
59+
failure.errors.each do |error_message|
60+
puts " - #{error_message}"
61+
end
62+
end
63+
end
64+
end
65+
end
66+
end

lib/perron/tasks/validate.rake

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
namespace :perron do
2+
desc "Validate all site resources"
3+
task validate: :environment do
4+
Perron::Site.validate
5+
end
6+
end
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
title: Invalid
3+
---
4+
5+
This page is missing a description and this should not validate.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,5 @@
11
class Content::Page < Perron::Resource
2+
delegate :title, :description, to: :metadata
3+
4+
validates :description, presence: true
25
end

test/perron/resource_test.rb

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@
33
class Perron::Site::ResourceTest < ActiveSupport::TestCase
44
setup do
55
@page_path = "test/dummy/app/content/pages/about.md"
6+
@invalid_page = "test/dummy/app/content/pages/invalid.md"
67
@post_path = "test/dummy/app/content/posts/2023-05-15-sample-post.md"
78
@inline_erb_post_path = "test/dummy/app/content/posts/2025-10-01-inline-erb-post.md"
89
@page = Content::Page.new(@page_path)
10+
@invalid_page = Content::Page.new(@invalid_page)
911
@post = Content::Post.new(@post_path)
10-
@inline_erb_post = Content::Post.new(@inline_erb_post_path) # <-- Addition
12+
@inline_erb_post = Content::Post.new(@inline_erb_post_path)
1113
end
1214

1315
test "initialization sets file_path" do
@@ -70,4 +72,30 @@ class Perron::Site::ResourceTest < ActiveSupport::TestCase
7072
test "#to_partial_path returns the conventional path from a nested logical name" do
7173
assert_equal "content/posts/post", @post.to_partial_path
7274
end
75+
76+
test "#valid? returns true for valid page" do
77+
assert_equal @page.valid?, true
78+
end
79+
80+
test "#valid? returns false for invalid page" do
81+
assert_equal @invalid_page.valid?, false
82+
end
83+
84+
test "#validate returns true for valid page" do
85+
assert_equal @page.validate, true
86+
end
87+
88+
test "#validate returns false for invalid page" do
89+
assert_equal @invalid_page.validate, false
90+
end
91+
92+
test "#validate! returns true for valid page" do
93+
assert_equal @page.validate!, true
94+
end
95+
96+
test "#validate! returns false for invalid page" do
97+
assert_raises(ActiveModel::ValidationError) { @invalid_page.validate! }
98+
assert @invalid_page.errors.any?
99+
assert_includes @invalid_page.errors.full_messages, "Description can't be blank"
100+
end
73101
end

0 commit comments

Comments
 (0)