From 1c9f0f27c7e353b22371c30f2d03b2dc7252d83a Mon Sep 17 00:00:00 2001 From: Morgan Aubert Date: Sat, 6 Jan 2024 15:53:45 -0500 Subject: [PATCH] Explicitly define allowed template attributes for query sets and query pages --- .../marten/template/ext/db/query/page_spec.cr | 149 ++++++++++++++++++ spec/marten/template/ext/db/query/set_spec.cr | 149 ++++++++++++++++++ .../template/ext/db/query/spec_helper.cr | 1 + src/marten/template/ext/db/query/page.cr | 33 +++- src/marten/template/ext/db/query/set.cr | 41 ++++- 5 files changed, 371 insertions(+), 2 deletions(-) create mode 100644 spec/marten/template/ext/db/query/page_spec.cr create mode 100644 spec/marten/template/ext/db/query/set_spec.cr create mode 100644 spec/marten/template/ext/db/query/spec_helper.cr diff --git a/spec/marten/template/ext/db/query/page_spec.cr b/spec/marten/template/ext/db/query/page_spec.cr new file mode 100644 index 000000000..de5f055f5 --- /dev/null +++ b/spec/marten/template/ext/db/query/page_spec.cr @@ -0,0 +1,149 @@ +require "./spec_helper" + +describe Marten::DB::Query::Page do + describe "#resolve_template_attribute" do + it "returns the expected result when requesting the 'all?' attribute" do + tag = Tag.create!(name: "a_tag", is_active: true) + paginator = Tag.all.order(:name).paginator(2) + page = Marten::DB::Query::Page(Tag).new([tag], 2, paginator) + + page.resolve_template_attribute("all?").should be_true + end + + it "returns the expected result when requesting the 'any?' attribute" do + tag = Tag.create!(name: "a_tag", is_active: true) + paginator = Tag.all.order(:name).paginator(2) + + Marten::DB::Query::Page(Tag).new([tag], 2, paginator).resolve_template_attribute("any?").should be_true + Marten::DB::Query::Page(Tag).new([] of Tag, 2, paginator).resolve_template_attribute("any?").should be_false + end + + it "returns the expected result when requesting the 'count' attribute" do + tag = Tag.create!(name: "a_tag", is_active: true) + paginator = Tag.all.order(:name).paginator(2) + + Marten::DB::Query::Page(Tag).new([tag], 2, paginator).resolve_template_attribute("count").should eq 1 + Marten::DB::Query::Page(Tag).new([] of Tag, 2, paginator).resolve_template_attribute("count").should eq 0 + end + + it "returns the expected result when requesting the 'empty?' attribute" do + tag = Tag.create!(name: "a_tag", is_active: true) + paginator = Tag.all.order(:name).paginator(2) + + Marten::DB::Query::Page(Tag).new([tag], 2, paginator).resolve_template_attribute("empty?").should be_false + Marten::DB::Query::Page(Tag).new([] of Tag, 2, paginator).resolve_template_attribute("empty?").should be_true + end + + it "returns the expected result when requesting the 'first?' attribute" do + tag = Tag.create!(name: "a_tag", is_active: true) + paginator = Tag.all.order(:name).paginator(2) + + Marten::DB::Query::Page(Tag).new([tag], 2, paginator).resolve_template_attribute("first?").should eq tag + Marten::DB::Query::Page(Tag).new([] of Tag, 2, paginator).resolve_template_attribute("first?").should be_nil + end + + it "returns the expected result when requesting the 'next_page?' attribute" do + tag_1 = Tag.create!(name: "a_tag", is_active: true) + tag_2 = Tag.create!(name: "b_tag", is_active: true) + tag_3 = Tag.create!(name: "c_tag", is_active: true) + tag_4 = Tag.create!(name: "d_tag", is_active: true) + Tag.create!(name: "e_tag", is_active: true) + + paginator = Tag.all.order(:name).paginator(2) + + page_1 = Marten::DB::Query::Page(Tag).new([tag_1, tag_2], 1, paginator) + page_1.resolve_template_attribute("next_page?").should be_true + + page_2 = Marten::DB::Query::Page(Tag).new([tag_3, tag_4], 3, paginator) + page_2.resolve_template_attribute("next_page?").should be_false + end + + it "returns the expected result when requesting the 'next_page_number' attribute" do + tag_1 = Tag.create!(name: "a_tag", is_active: true) + tag_2 = Tag.create!(name: "b_tag", is_active: true) + tag_3 = Tag.create!(name: "c_tag", is_active: true) + tag_4 = Tag.create!(name: "d_tag", is_active: true) + Tag.create!(name: "e_tag", is_active: true) + + paginator = Tag.all.order(:name).paginator(2) + + page_1 = Marten::DB::Query::Page(Tag).new([tag_1, tag_2], 1, paginator) + page_1.resolve_template_attribute("next_page_number").should eq 2 + + page_2 = Marten::DB::Query::Page(Tag).new([tag_3, tag_4], 3, paginator) + page_2.resolve_template_attribute("next_page_number").should be_nil + end + + it "returns the expected result when requesting the 'none?' attribute" do + tag = Tag.create!(name: "a_tag", is_active: true) + paginator = Tag.all.order(:name).paginator(2) + + Marten::DB::Query::Page(Tag).new([tag], 2, paginator).resolve_template_attribute("none?").should be_false + Marten::DB::Query::Page(Tag).new([] of Tag, 2, paginator).resolve_template_attribute("none?").should be_true + end + + it "returns the expected result when requesting the 'number' attribute" do + tag_1 = Tag.create!(name: "a_tag", is_active: true) + tag_2 = Tag.create!(name: "b_tag", is_active: true) + tag_3 = Tag.create!(name: "c_tag", is_active: true) + tag_4 = Tag.create!(name: "d_tag", is_active: true) + Tag.create!(name: "e_tag", is_active: true) + + paginator = Tag.all.order(:name).paginator(2) + + page_1 = Marten::DB::Query::Page(Tag).new([tag_1, tag_2], 1, paginator) + page_1.resolve_template_attribute("number").should eq 1 + + page_2 = Marten::DB::Query::Page(Tag).new([tag_3, tag_4], 3, paginator) + page_2.resolve_template_attribute("number").should eq 3 + end + + it "returns the expected result when requesting the 'one?' attribute" do + tag = Tag.create!(name: "a_tag", is_active: true) + paginator = Tag.all.order(:name).paginator(2) + + Marten::DB::Query::Page(Tag).new([tag], 2, paginator).resolve_template_attribute("any?").should be_true + Marten::DB::Query::Page(Tag).new([] of Tag, 2, paginator).resolve_template_attribute("any?").should be_false + end + + it "returns the expected result when requesting the 'previous_page?' attribute" do + tag_1 = Tag.create!(name: "a_tag", is_active: true) + tag_2 = Tag.create!(name: "b_tag", is_active: true) + tag_3 = Tag.create!(name: "c_tag", is_active: true) + tag_4 = Tag.create!(name: "d_tag", is_active: true) + Tag.create!(name: "e_tag", is_active: true) + + paginator = Tag.all.order(:name).paginator(2) + + page_1 = Marten::DB::Query::Page(Tag).new([tag_1, tag_2], 1, paginator) + page_1.resolve_template_attribute("previous_page?").should be_false + + page_2 = Marten::DB::Query::Page(Tag).new([tag_3, tag_4], 3, paginator) + page_2.resolve_template_attribute("previous_page?").should be_true + end + + it "returns the expected result when requesting the 'previous_page_number' attribute" do + tag_1 = Tag.create!(name: "a_tag", is_active: true) + tag_2 = Tag.create!(name: "b_tag", is_active: true) + tag_3 = Tag.create!(name: "c_tag", is_active: true) + tag_4 = Tag.create!(name: "d_tag", is_active: true) + Tag.create!(name: "e_tag", is_active: true) + + paginator = Tag.all.order(:name).paginator(2) + + page_1 = Marten::DB::Query::Page(Tag).new([tag_1, tag_2], 1, paginator) + page_1.resolve_template_attribute("previous_page_number").should be_nil + + page_2 = Marten::DB::Query::Page(Tag).new([tag_3, tag_4], 3, paginator) + page_2.resolve_template_attribute("previous_page_number").should eq 2 + end + + it "returns the expected result when requesting the 'size' attribute" do + tag = Tag.create!(name: "a_tag", is_active: true) + paginator = Tag.all.order(:name).paginator(2) + + Marten::DB::Query::Page(Tag).new([tag], 2, paginator).resolve_template_attribute("size").should eq 1 + Marten::DB::Query::Page(Tag).new([] of Tag, 2, paginator).resolve_template_attribute("size").should eq 0 + end + end +end diff --git a/spec/marten/template/ext/db/query/set_spec.cr b/spec/marten/template/ext/db/query/set_spec.cr new file mode 100644 index 000000000..cbf8f03f4 --- /dev/null +++ b/spec/marten/template/ext/db/query/set_spec.cr @@ -0,0 +1,149 @@ +require "./spec_helper" + +describe Marten::DB::Query::Set do + describe "#resolve_template_attribute" do + it "returns the expected result when requesting the 'all' attribute" do + tag_1 = Tag.create!(name: "tag_1", is_active: true) + tag_2 = Tag.create!(name: "tag_2", is_active: true) + + result = Tag.all.resolve_template_attribute("all") + result.should be_a Marten::DB::Query::Set(Tag) + result = result.as(Marten::DB::Query::Set(Tag)) + result.to_set.should eq([tag_1, tag_2].to_set) + end + + it "returns the expected result when requesting the 'all?' attribute" do + Tag.create!(name: "tag_1", is_active: true) + Tag.create!(name: "tag_2", is_active: true) + + Tag.all.resolve_template_attribute("all?").should be_true + end + + it "returns the expected result when requesting the 'any?' attribute" do + Tag.create!(name: "tag_1", is_active: true) + Tag.create!(name: "tag_2", is_active: true) + + Tag.all.resolve_template_attribute("any?").should be_true + Tag.filter(name: "unknown").resolve_template_attribute("any?").should be_false + end + + it "returns the expected result when requesting the 'count' attribute" do + Tag.create!(name: "tag_1", is_active: true) + Tag.create!(name: "tag_2", is_active: true) + + Tag.all.resolve_template_attribute("count").should eq 2 + Tag.filter(name: "unknown").resolve_template_attribute("count").should eq 0 + end + + it "returns the expected result when requesting the 'distinct' attribute" do + user_1 = TestUser.create!(username: "jd1", email: "jd1@example.com", first_name: "John", last_name: "Doe") + user_2 = TestUser.create!(username: "jd2", email: "jd2@example.com", first_name: "John", last_name: "Doe") + + Post.create!(author: user_1, title: "Post 1", published: true) + Post.create!(author: user_1, title: "Post 2", published: true) + Post.create!(author: user_2, title: "Post 3", published: true) + Post.create!(author: user_1, title: "Post 4", published: false) + + result = TestUser.filter(posts__published: true).resolve_template_attribute("distinct") + result.should be_a Marten::DB::Query::Set(TestUser) + result = result.as(Marten::DB::Query::Set(TestUser)) + result.to_set.should eq [user_1, user_2].to_set + end + + it "returns the expected result when requesting the 'empty?' attribute" do + Tag.create!(name: "tag_1", is_active: true) + Tag.create!(name: "tag_2", is_active: true) + + Tag.all.resolve_template_attribute("empty?").should be_false + Tag.filter(name: "unknown").resolve_template_attribute("empty?").should be_true + end + + it "returns the expected result when requesting the 'exists?' attribute" do + Tag.create!(name: "tag_1", is_active: true) + Tag.create!(name: "tag_2", is_active: true) + + Tag.all.resolve_template_attribute("exists?").should be_true + Tag.filter(name: "unknown").resolve_template_attribute("exists?").should be_false + end + + it "returns the expected result when requesting the 'first' attribute" do + tag_1 = Tag.create!(name: "tag_1", is_active: true) + Tag.create!(name: "tag_2", is_active: true) + + Tag.all.order(:id).resolve_template_attribute("first").should eq tag_1 + Tag.filter(name: "unknown").resolve_template_attribute("first").should be_nil + end + + it "returns the expected result when requesting the 'first!' attribute" do + tag_1 = Tag.create!(name: "tag_1", is_active: true) + Tag.create!(name: "tag_2", is_active: true) + + Tag.all.order(:id).resolve_template_attribute("first!").should eq tag_1 + end + + it "returns the expected result when requesting the 'first?' attribute" do + tag_1 = Tag.create!(name: "tag_1", is_active: true) + Tag.create!(name: "tag_2", is_active: true) + + Tag.all.order(:id).resolve_template_attribute("first?").should eq tag_1 + Tag.filter(name: "unknown").resolve_template_attribute("first?").should be_nil + end + + it "returns the expected result when requesting the 'last' attribute" do + Tag.create!(name: "tag_1", is_active: true) + tag_2 = Tag.create!(name: "tag_2", is_active: true) + + Tag.all.order(:id).resolve_template_attribute("last").should eq tag_2 + Tag.filter(name: "unknown").resolve_template_attribute("last").should be_nil + end + + it "returns the expected result when requesting the 'last!' attribute" do + Tag.create!(name: "tag_1", is_active: true) + tag_2 = Tag.create!(name: "tag_2", is_active: true) + + Tag.all.order(:id).resolve_template_attribute("last!").should eq tag_2 + end + + it "returns the expected result when requesting the 'none' attribute" do + Tag.create!(name: "tag_1", is_active: true) + Tag.create!(name: "tag_2", is_active: true) + + result = Tag.all.resolve_template_attribute("none") + result.should be_a Marten::DB::Query::Set(Tag) + result = result.as(Marten::DB::Query::Set(Tag)) + result.exists?.should be_false + end + + it "returns the expected result when requesting the 'none?' attribute" do + Tag.create!(name: "tag_1", is_active: true) + Tag.create!(name: "tag_2", is_active: true) + + Tag.all.resolve_template_attribute("none?").should be_false + Tag.filter(name: "unknown").resolve_template_attribute("none?").should be_true + end + + it "returns the expected result when requesting the 'one?' attribute" do + Tag.create!(name: "tag_1", is_active: true) + + Tag.all.resolve_template_attribute("one?").should be_true + Tag.filter(name: "unknown").resolve_template_attribute("one?").should be_false + end + + it "returns the expected result when requesting the 'reverse' attribute" do + tag_1 = Tag.create!(name: "tag_1", is_active: true) + tag_2 = Tag.create!(name: "tag_2", is_active: true) + + result = Tag.all.order(:id).resolve_template_attribute("reverse") + result.should be_a Marten::DB::Query::Set(Tag) + result = result.as(Marten::DB::Query::Set(Tag)) + result.to_a.should eq [tag_2, tag_1] + end + + it "returns the expected result when requesting the 'size' attribute" do + Tag.create!(name: "tag_1", is_active: true) + Tag.create!(name: "tag_2", is_active: true) + + Tag.all.resolve_template_attribute("size").should eq 2 + end + end +end diff --git a/spec/marten/template/ext/db/query/spec_helper.cr b/spec/marten/template/ext/db/query/spec_helper.cr new file mode 100644 index 000000000..cba784c4c --- /dev/null +++ b/spec/marten/template/ext/db/query/spec_helper.cr @@ -0,0 +1 @@ +require "../spec_helper" diff --git a/src/marten/template/ext/db/query/page.cr b/src/marten/template/ext/db/query/page.cr index 9a5140e3d..c6517b1a1 100644 --- a/src/marten/template/ext/db/query/page.cr +++ b/src/marten/template/ext/db/query/page.cr @@ -1,3 +1,34 @@ class Marten::DB::Query::Page(M) - include Marten::Template::Object::Auto + include Marten::Template::Object + + def resolve_template_attribute(key : String) + case key + when "all?" + all? + when "any?" + any? + when "count" + count + when "empty?" + empty? + when "first?" + first? + when "next_page?" + next_page? + when "next_page_number" + next_page_number + when "none?" + none? + when "number" + number + when "one?" + one? + when "previous_page?" + previous_page? + when "previous_page_number" + previous_page_number + when "size" + size + end + end end diff --git a/src/marten/template/ext/db/query/set.cr b/src/marten/template/ext/db/query/set.cr index fa7ef50f6..f70537001 100644 --- a/src/marten/template/ext/db/query/set.cr +++ b/src/marten/template/ext/db/query/set.cr @@ -1,3 +1,42 @@ class Marten::DB::Query::Set(M) - include Marten::Template::Object::Auto + include Marten::Template::Object + + def resolve_template_attribute(key : String) + case key + when "all" + all + when "all?" + all? + when "any?" + any? + when "count" + count + when "distinct" + distinct + when "empty?" + empty? + when "exists?" + exists? + when "first" + first + when "first!" + first! + when "first?" + first? + when "last" + last + when "last!" + last! + when "none" + none + when "none?" + none? + when "one?" + one? + when "reverse" + reverse + when "size" + size + end + end end