From 4ce8af496d4b32949d34963d015872130b396f19 Mon Sep 17 00:00:00 2001 From: Davide Paolo Tua Date: Tue, 25 Oct 2022 11:31:23 +0200 Subject: [PATCH 1/6] Add updated? and created? methods on SaveOperations Implements #888. Adds two new boolean methods to SaveOperation. They work similarly to new_record? but always return false if the record was not persisted (e.g: failed save or not-yet-performed operation) --- spec/avram/operations/save_operation_spec.cr | 58 +++++++++++++++++++- src/avram/save_operation.cr | 8 +++ 2 files changed, 64 insertions(+), 2 deletions(-) diff --git a/spec/avram/operations/save_operation_spec.cr b/spec/avram/operations/save_operation_spec.cr index f22c62248..4348e1b0e 100644 --- a/spec/avram/operations/save_operation_spec.cr +++ b/spec/avram/operations/save_operation_spec.cr @@ -220,7 +220,9 @@ describe "Avram::SaveOperation" do nickname: nil, age: 30, joined_at: joined_at - ) do |_operation, user| + ) do |operation, user| + operation.created?.should be_false + operation.updated?.should be_true UserQuery.new.select_count.should eq(1) user = user.not_nil! user.id.should eq(existing_user.id) @@ -241,7 +243,9 @@ describe "Avram::SaveOperation" do nickname: "R.", age: 30, joined_at: joined_at - ) do |_operation, user| + ) do |operation, user| + operation.created?.should be_true + operation.updated?.should be_false UserQuery.new.select_count.should eq(2) # Keep existing user the same user_with_different_nickname.age.should eq(20) @@ -333,6 +337,8 @@ describe "Avram::SaveOperation" do operation.save_failed?.should be_true operation.save_status.should eq(SaveUser::OperationStatus::SaveFailed) operation.valid?.should be_false + operation.created?.should be_false + operation.updated?.should be_false end it "is false if the object is not marked as saved but no action was performed" do @@ -342,6 +348,8 @@ describe "Avram::SaveOperation" do operation.save_status.should eq(SaveUser::OperationStatus::Unperformed) operation.saved?.should be_false operation.valid?.should be_false + operation.created?.should be_false + operation.updated?.should be_false end end @@ -849,6 +857,52 @@ describe "Avram::SaveOperation" do end end + describe "#created?" do + context "after creating" do + it "returns 'true'" do + operation = SaveUser.new(name: "Dan", age: 34, joined_at: Time.utc) + + operation.created?.should be_false + operation.save.should be_true + operation.created?.should be_true + end + end + + context "after updating" do + it "returns 'false'" do + user = UserFactory.create &.name("Dan").age(34).joined_at(Time.utc) + operation = SaveUser.new(user, name: "Tom") + + operation.created?.should be_false + operation.save.should be_true + operation.created?.should be_false + end + end + end + + describe "#updated?" do + context "after creating" do + it "returns 'false'" do + operation = SaveUser.new(name: "Dan", age: 34, joined_at: Time.utc) + + operation.updated?.should be_false + operation.save.should be_true + operation.updated?.should be_false + end + end + + context "after updating" do + it "returns 'true'" do + user = UserFactory.create &.name("Dan").age(34).joined_at(Time.utc) + operation = SaveUser.new(user, name: "Tom") + + operation.updated?.should be_false + operation.save.should be_true + operation.updated?.should be_true + end + end + end + describe "skip_default_validations" do it "allows blank strings to be saved" do post = PostFactory.create diff --git a/src/avram/save_operation.cr b/src/avram/save_operation.cr index d6d5dff87..c2e23c4cb 100644 --- a/src/avram/save_operation.cr +++ b/src/avram/save_operation.cr @@ -132,6 +132,14 @@ abstract class Avram::SaveOperation(T) save_status == OperationStatus::Saved end + def created? + saved? && new_record? + end + + def updated? + saved? && !new_record? + end + # Return true if the operation has run and the record failed to save def save_failed? save_status == OperationStatus::SaveFailed From 507868846a018bbf9da6e075deb7f1e26660287e Mon Sep 17 00:00:00 2001 From: Davide Paolo Tua Date: Thu, 1 Dec 2022 14:04:29 +0100 Subject: [PATCH 2/6] Implement date function --- spec/avram/time_criteria_spec.cr | 5 +++++ src/avram/charms/time_extensions.cr | 4 ++++ src/avram/criteria_extensions/between_criteria.cr | 2 +- src/avram/define_attribute.cr | 1 - src/avram/model.cr | 2 +- 5 files changed, 11 insertions(+), 3 deletions(-) diff --git a/spec/avram/time_criteria_spec.cr b/spec/avram/time_criteria_spec.cr index 10d113802..9703c6fc2 100644 --- a/spec/avram/time_criteria_spec.cr +++ b/spec/avram/time_criteria_spec.cr @@ -14,6 +14,11 @@ describe Time::Lucky::Criteria do end end + it "in_date" do + input_date = "2012-01-31" + activated_at.as_date.eq(input_date).to_sql.should eq ["SELECT users.id, users.created_at, users.updated_at, users.activated_at FROM users WHERE DATE(users.activated_at) = $1", input_date] + end + describe "extract" do it "fails with non supported symbol" do expect_raises(ArgumentError) { activated_at.extract(:dayz).eq(5).to_sql } diff --git a/src/avram/charms/time_extensions.cr b/src/avram/charms/time_extensions.cr index 1bfc508cd..0869c5191 100644 --- a/src/avram/charms/time_extensions.cr +++ b/src/avram/charms/time_extensions.cr @@ -78,6 +78,10 @@ struct Time class Criteria(T, V) < Avram::Criteria(T, V) include Avram::BetweenCriteria(T, V) include Avram::ExtractCriteria + + def as_date + Criteria(T, String).new(rows, "DATE(#{@column})") + end end end end diff --git a/src/avram/criteria_extensions/between_criteria.cr b/src/avram/criteria_extensions/between_criteria.cr index 673a332bc..13ff9b743 100644 --- a/src/avram/criteria_extensions/between_criteria.cr +++ b/src/avram/criteria_extensions/between_criteria.cr @@ -4,7 +4,7 @@ module Avram::BetweenCriteria(T, V) def between(low_value : V, high_value : V) add_clauses([ Avram::Where::GreaterThanOrEqualTo.new(@column, V.adapter.to_db!(low_value)), - Avram::Where::LessThanOrEqualTo.new(@column, V.adapter.to_db!(high_value)) + Avram::Where::LessThanOrEqualTo.new(@column, V.adapter.to_db!(high_value)), ]) end end diff --git a/src/avram/define_attribute.cr b/src/avram/define_attribute.cr index 9ed6e3e5d..5cfd4fcc4 100644 --- a/src/avram/define_attribute.cr +++ b/src/avram/define_attribute.cr @@ -20,7 +20,6 @@ module Avram::DefineAttribute ATTRIBUTES = [] of Nil \{% end %} - \{% if !@type.ancestors.first.abstract? %} \{% for attribute in @type.ancestors.first.constant :ATTRIBUTES %} \{% ATTRIBUTES << attribute %} diff --git a/src/avram/model.cr b/src/avram/model.cr index 7feb2abaf..900d28136 100644 --- a/src/avram/model.cr +++ b/src/avram/model.cr @@ -16,7 +16,7 @@ abstract class Avram::Model end macro inherited - COLUMNS = [] of Nil # types are not checked in macros + COLUMNS = [] of Nil # types are not checked in macros ASSOCIATIONS = [] of Nil # types are not checked in macros include LuckyCache::Cachable end From a1f93adc063a6086e3d29a87b23c936af487f4e5 Mon Sep 17 00:00:00 2001 From: Davide Paolo Tua Date: Thu, 1 Dec 2022 14:12:11 +0100 Subject: [PATCH 3/6] Move upper and lower to new criterias --- src/avram/charms/string_extensions.cr | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/src/avram/charms/string_extensions.cr b/src/avram/charms/string_extensions.cr index 1aa0076c6..4aa69d064 100644 --- a/src/avram/charms/string_extensions.cr +++ b/src/avram/charms/string_extensions.cr @@ -41,23 +41,11 @@ class String end def upper - @upper = true - self + Criteria(T, V).new(rows, "UPPER(#{column})") end def lower - @lower = true - self - end - - def column - if @upper - "UPPER(#{@column})" - elsif @lower - "LOWER(#{@column})" - else - @column - end + Criteria(T, V).new(rows, "LOWER(#{column})") end end end From 09c82f5d63f3d635746f06364226e25ad5d3aaea Mon Sep 17 00:00:00 2001 From: Davide Paolo Tua Date: Thu, 1 Dec 2022 16:17:13 +0100 Subject: [PATCH 4/6] generalize the application of functions to columns --- spec/avram/criteria_spec.cr | 6 ++++++ spec/avram/string_criteria_spec.cr | 6 ++++++ src/avram/charms/string_extensions.cr | 12 +++--------- src/avram/charms/time_extensions.cr | 4 +--- src/avram/criteria.cr | 16 ++++++++++++++++ 5 files changed, 32 insertions(+), 12 deletions(-) diff --git a/spec/avram/criteria_spec.cr b/spec/avram/criteria_spec.cr index 38827a9bb..3b6f92b58 100644 --- a/spec/avram/criteria_spec.cr +++ b/spec/avram/criteria_spec.cr @@ -75,6 +75,12 @@ describe Avram::Criteria do age.not.gt("3").age.eq("20").to_sql.should eq ["SELECT #{QueryMe::COLUMN_SQL} FROM users WHERE users.age <= $1 AND users.age = $2", "3", "20"] end end + + describe "with_default" do + it "provides a default value to the column in case it is null" do + age.with_default(5).gt(18).to_sql.should eq ["SELECT #{QueryMe::COLUMN_SQL} FROM users WHERE COALESCE(users.age, 5) > $1", "18"] + end + end end private def age diff --git a/spec/avram/string_criteria_spec.cr b/spec/avram/string_criteria_spec.cr index 3833c54cc..25621e696 100644 --- a/spec/avram/string_criteria_spec.cr +++ b/spec/avram/string_criteria_spec.cr @@ -33,6 +33,12 @@ describe String::Lucky::Criteria do end end + describe "trim" do + it "uses TRIM" do + name.trim.eq("elon").to_sql.should eq ["SELECT #{QueryMe::COLUMN_SQL} FROM users WHERE TRIM(users.name) = $1", "elon"] + end + end + describe "not" do describe "with chained criteria" do it "negates the following criteria" do diff --git a/src/avram/charms/string_extensions.cr b/src/avram/charms/string_extensions.cr index 4aa69d064..2acf9f8df 100644 --- a/src/avram/charms/string_extensions.cr +++ b/src/avram/charms/string_extensions.cr @@ -29,8 +29,6 @@ class String class Criteria(T, V) < Avram::Criteria(T, V) include Avram::IncludesCriteria(T, V) - @upper = false - @lower = false def like(value : String) : T add_clause(Avram::Where::Like.new(column, value)) @@ -40,13 +38,9 @@ class String add_clause(Avram::Where::Ilike.new(column, value)) end - def upper - Criteria(T, V).new(rows, "UPPER(#{column})") - end - - def lower - Criteria(T, V).new(rows, "LOWER(#{column})") - end + define_function_criteria(upper, V) + define_function_criteria(lower, V) + define_function_criteria(trim, String) end end end diff --git a/src/avram/charms/time_extensions.cr b/src/avram/charms/time_extensions.cr index 0869c5191..11a9fec00 100644 --- a/src/avram/charms/time_extensions.cr +++ b/src/avram/charms/time_extensions.cr @@ -79,9 +79,7 @@ struct Time include Avram::BetweenCriteria(T, V) include Avram::ExtractCriteria - def as_date - Criteria(T, String).new(rows, "DATE(#{@column})") - end + define_function_criteria(as_date, String, "DATE") end end end diff --git a/src/avram/criteria.cr b/src/avram/criteria.cr index 40b4fda7a..02c6186ca 100644 --- a/src/avram/criteria.cr +++ b/src/avram/criteria.cr @@ -185,4 +185,20 @@ class Avram::Criteria(T, V) sql_clause end end + + macro define_function_criteria(name, output_type = V, sql_name = nil) + {% sql_name = sql_name ? sql_name.id : name.id.upcase %} + def {{name}} + Criteria(T,{{output_type}}).new(rows, "{{sql_name}}(#{column})") + end + end + + macro define_function_with_additional_argument_criteria(name, output_type = V, sql_name = nil) + {% sql_name = sql_name ? sql_name.id : name.id.upcase %} + def {{name}}(additional_argument : V) + Criteria(T,{{output_type}}).new(rows, "{{sql_name}}(#{column}, #{additional_argument})") + end + end + + define_function_with_additional_argument_criteria(with_default, V, "COALESCE") end From 6774dae40b564a05c15b73040b731e923952b7aa Mon Sep 17 00:00:00 2001 From: Davide Paolo Tua Date: Thu, 1 Dec 2022 16:24:21 +0100 Subject: [PATCH 5/6] Put in standby the extra-param function creation --- spec/avram/criteria_spec.cr | 10 ++++------ src/avram/criteria.cr | 9 --------- 2 files changed, 4 insertions(+), 15 deletions(-) diff --git a/spec/avram/criteria_spec.cr b/spec/avram/criteria_spec.cr index 3b6f92b58..30c09b477 100644 --- a/spec/avram/criteria_spec.cr +++ b/spec/avram/criteria_spec.cr @@ -75,12 +75,6 @@ describe Avram::Criteria do age.not.gt("3").age.eq("20").to_sql.should eq ["SELECT #{QueryMe::COLUMN_SQL} FROM users WHERE users.age <= $1 AND users.age = $2", "3", "20"] end end - - describe "with_default" do - it "provides a default value to the column in case it is null" do - age.with_default(5).gt(18).to_sql.should eq ["SELECT #{QueryMe::COLUMN_SQL} FROM users WHERE COALESCE(users.age, 5) > $1", "18"] - end - end end private def age @@ -90,3 +84,7 @@ end private def nickname QueryMe::BaseQuery.new.nickname end + +private def created_at + QueryMe::BaseQuery.new.created_at +end diff --git a/src/avram/criteria.cr b/src/avram/criteria.cr index 02c6186ca..e452ab903 100644 --- a/src/avram/criteria.cr +++ b/src/avram/criteria.cr @@ -192,13 +192,4 @@ class Avram::Criteria(T, V) Criteria(T,{{output_type}}).new(rows, "{{sql_name}}(#{column})") end end - - macro define_function_with_additional_argument_criteria(name, output_type = V, sql_name = nil) - {% sql_name = sql_name ? sql_name.id : name.id.upcase %} - def {{name}}(additional_argument : V) - Criteria(T,{{output_type}}).new(rows, "{{sql_name}}(#{column}, #{additional_argument})") - end - end - - define_function_with_additional_argument_criteria(with_default, V, "COALESCE") end From 5985d256ea998d5d5bb5f9d9085ce5082283617f Mon Sep 17 00:00:00 2001 From: Davide Paolo Tua Date: Thu, 1 Dec 2022 16:27:18 +0100 Subject: [PATCH 6/6] Final clean up --- spec/avram/criteria_spec.cr | 4 ---- spec/avram/time_criteria_spec.cr | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/spec/avram/criteria_spec.cr b/spec/avram/criteria_spec.cr index 30c09b477..38827a9bb 100644 --- a/spec/avram/criteria_spec.cr +++ b/spec/avram/criteria_spec.cr @@ -84,7 +84,3 @@ end private def nickname QueryMe::BaseQuery.new.nickname end - -private def created_at - QueryMe::BaseQuery.new.created_at -end diff --git a/spec/avram/time_criteria_spec.cr b/spec/avram/time_criteria_spec.cr index 9703c6fc2..13074e1e0 100644 --- a/spec/avram/time_criteria_spec.cr +++ b/spec/avram/time_criteria_spec.cr @@ -14,7 +14,7 @@ describe Time::Lucky::Criteria do end end - it "in_date" do + it "as_date" do input_date = "2012-01-31" activated_at.as_date.eq(input_date).to_sql.should eq ["SELECT users.id, users.created_at, users.updated_at, users.activated_at FROM users WHERE DATE(users.activated_at) = $1", input_date] end