diff --git a/spec/marten/db/model/querying_spec.cr b/spec/marten/db/model/querying_spec.cr index 0f8425109..b16f10809 100644 --- a/spec/marten/db/model/querying_spec.cr +++ b/spec/marten/db/model/querying_spec.cr @@ -437,6 +437,60 @@ describe Marten::DB::Model::Querying do it "makes use of the default queryset when using a block defining an advanced predicates expression" do Tag.get { q(name: "crystal") }.should be_nil end + + it "returns the object corresponding to the raw SQL predicate" do + user = TestUser.create!(username: "jd3", email: "jd3@example.com", first_name: "John", last_name: "Doe") + TestUser.get("username = 'jd3'").should eq user + end + + it "returns the object corresponding to the raw SQL predicate with positional arguments" do + user = TestUser.create!(username: "jd3", email: "jd3@example.com", first_name: "John", last_name: "Doe") + TestUser.get("username = ?", "jd3").should eq user + end + + it "returns nil if no record matches the raw SQL predicate with positional arguments" do + TestUser.get("username = ?", "unknown").should be_nil + end + + it "returns the object when parameters are passed as an array" do + tag = Tag.create!(name: "elixir", is_active: true) + Tag.get("name = ? AND is_active = ?", ["elixir", true]).should eq tag + end + + it "returns nil when no record matches and parameters are passed as an array" do + Tag.get("name = ? AND is_active = ?", ["nonexistent", true]).should be_nil + end + + it "raises an error for an invalid SQL column in raw predicate" do + expect_raises(Exception) { TestUser.get("invalid_column = ?", "jd1") } + end + + it "returns the object using a raw SQL predicate with named parameters" do + tag = Tag.create!(name: "custom", is_active: true) + Tag.get("name = :name AND is_active = :active", name: "custom", active: true).should eq tag + end + + it "returns nil if no record matches the raw SQL predicate with named parameters" do + Tag.get("name = :name AND is_active = :active", name: "nonexistent", active: false).should be_nil + end + + it "returns the object when parameters are passed as a named tuple" do + tag = Tag.create!(name: "rust", is_active: true) + Tag.get("name = :name AND is_active = :active", {name: "rust", active: true}).should eq tag + end + + it "returns nil when no record matches and parameters are passed as a named tuple" do + Tag.get("name = :name AND is_active = :active", {name: "nonexistent", active: false}).should be_nil + end + + it "returns the object when parameters are passed as a hash" do + tag = Tag.create!(name: "python", is_active: true) + Tag.get("name = :name AND is_active = :active", {"name" => "python", "active" => true}).should eq tag + end + + it "returns nil when no record matches and parameters are passed as a hash" do + Tag.get("name = :name AND is_active = :active", {"name" => "nonexistent", "active" => false}).should be_nil + end end describe "::get!" do @@ -477,6 +531,72 @@ describe Marten::DB::Model::Querying do it "makes use of the default queryset when using a block defining an advanced predicates expression" do expect_raises(Marten::DB::Errors::RecordNotFound) { Tag.get! { q(name: "crystal") } } end + + it "returns the object using a raw SQL predicate" do + tag = Tag.create!(name: "elixir", is_active: true) + Tag.get!("name = 'elixir' AND is_active = true").should eq tag + end + + it "raises RecordNotFound when no record matches" do + expect_raises(Marten::DB::Errors::RecordNotFound) do + Tag.get!("name = 'nonexistent' AND is_active = true") + end + end + + it "returns the object using a raw SQL predicate with positional arguments" do + tag = Tag.create!(name: "elixir", is_active: true) + Tag.get!("name = ? AND is_active = ?", "elixir", true).should eq tag + end + + it "raises RecordNotFound when no record matches with positional arguments" do + expect_raises(Marten::DB::Errors::RecordNotFound) do + Tag.get!("name = ? AND is_active = ?", "nonexistent", true) + end + end + + it "returns the object using a raw SQL predicate with named arguments" do + tag = Tag.create!(name: "python", is_active: true) + Tag.get!("name = :name AND is_active = :active", name: "python", active: true).should eq tag + end + + it "raises RecordNotFound when no record matches with named arguments" do + expect_raises(Marten::DB::Errors::RecordNotFound) do + Tag.get!("name = :name AND is_active = :active", name: "nonexistent", active: false) + end + end + + it "returns the object when parameters are passed as an array" do + tag = Tag.create!(name: "elixir", is_active: true) + Tag.get!("name = ? AND is_active = ?", ["elixir", true]).should eq tag + end + + it "raises RecordNotFound when no record matches and parameters are passed as an array" do + expect_raises(Marten::DB::Errors::RecordNotFound) do + Tag.get!("name = ? AND is_active = ?", ["nonexistent", true]) + end + end + + it "returns the object when parameters are passed as a named tuple" do + tag = Tag.create!(name: "rust", is_active: true) + Tag.get!("name = :name AND is_active = :active", {name: "rust", active: true}).should eq tag + end + + it "raises RecordNotFound when no record matches and parameters are passed as a named tuple" do + expect_raises(Marten::DB::Errors::RecordNotFound) do + Tag.get!("name = :name AND is_active = :active", {name: "nonexistent", active: false}) + end + end + + it "returns the object when parameters are passed as a hash" do + tag = Tag.create!(name: "rust", is_active: true) + Tag.get!("name = :name AND is_active = :active", {"name" => "rust", "active" => true}).should eq tag + end + + it "raises RecordNotFound when no record matches and parameters are passed as a hash" do + expect_raises(Marten::DB::Errors::RecordNotFound) do + Tag.get!("name = :name AND is_active = :active", {"name" => "nonexistent", "active" => false}) + end + end end describe "::get_or_create" do diff --git a/src/marten/db/model/querying.cr b/src/marten/db/model/querying.cr index d647e506a..3c7d80710 100644 --- a/src/marten/db/model/querying.cr +++ b/src/marten/db/model/querying.cr @@ -326,6 +326,61 @@ module Marten default_queryset.get(**kwargs) end + # Returns a single model instance matching the given raw SQL condition. + # Returns `nil` if no record matches. + # + # Example: + # ``` + # post = Post.get("is_published = true") + # ``` + def get(raw_predicate : String) + default_queryset.get(raw_predicate) + end + + # Returns a single model instance matching the given raw SQL condition with positional arguments. + # Returns `nil` if no record matches. + # + # Example: + # ``` + # post = Post.get("name = ?", "crystal") + # ``` + def get(raw_predicate : String, *args) + default_queryset.get(raw_predicate, *args) + end + + # Returns a single model instance matching the given raw SQL condition with positional parameters. + # Returns `nil` if no record matches. + # + # Example: + # ``` + # post = Post.get("name = ? AND is_published = ?", ["crystal", true]) + # ``` + def get(raw_predicate : String, params : Array) + default_queryset.get(raw_predicate, params) + end + + # Returns a single model instance matching the given raw SQL condition with named parameters. + # Returns `nil` if no record matches. + # + # Example: + # ``` + # post = Post.get("name = :name AND is_published = :published", name: "crystal", published: true) + # ``` + def get(raw_predicate : String, **kwargs) + default_queryset.get(raw_predicate, **kwargs) + end + + # Returns a single model instance matching the given raw SQL condition with a named parameters hash. + # Returns `nil` if no record matches. + # + # Example: + # ``` + # post = Post.get("name = :name", {name: "crystal"}) + # ``` + def get(raw_predicate : String, params : Hash | NamedTuple) + default_queryset.get(raw_predicate, params) + end + # Returns the model instance matching a specific set of advanced filters. # # Model fields such as primary keys or fields with a unique constraint should be used here in order to @@ -365,6 +420,61 @@ module Marten default_queryset.get!(**kwargs) end + # Returns a single model instance matching the given raw SQL condition. + # Raises a `RecordNotFound` exception if no record matches. + # + # Example: + # ``` + # post = Post.get!("is_published = true") + # ``` + def get!(raw_predicate : String) + default_queryset.get!(raw_predicate) + end + + # Returns a single model instance matching the given raw SQL condition with positional arguments. + # Raises a `RecordNotFound` exception if no record matches. + # + # Example: + # ``` + # post = Post.get!("name = ?", "crystal") + # ``` + def get!(raw_predicate : String, *args) + default_queryset.get!(raw_predicate, *args) + end + + # Returns a single model instance matching the given raw SQL condition with positional parameters. + # Raises a `RecordNotFound` exception if no record matches. + # + # Example: + # ``` + # post = Post.get!("name = ? AND is_published = ?", ["crystal", true]) + # ``` + def get!(raw_predicate : String, params : Array) + default_queryset.get!(raw_predicate, params) + end + + # Returns a single model instance matching the given raw SQL condition with named parameters. + # Raises a `RecordNotFound` exception if no record matches. + # + # Example: + # ``` + # post = Post.get!("name = :name AND is_published = :published", name: "crystal", published: true) + # ``` + def get!(raw_predicate : String, **kwargs) + default_queryset.get!(raw_predicate, **kwargs) + end + + # Returns a single model instance matching the given raw SQL condition with a named parameters hash. + # Raises a `RecordNotFound` exception if no record matches. + # + # Example: + # ``` + # post = Post.get!("name = :name", {name: "crystal"}) + # ``` + def get!(raw_predicate : String, params : Hash | NamedTuple) + default_queryset.get!(raw_predicate, params) + end + # Returns the model instance matching a specific set of advanced filters. # # Model fields such as primary keys or fields with a unique constraint should be used here in order to