diff --git a/spec/operations/save_operation_spec.cr b/spec/operations/save_operation_spec.cr index 2b272f6b2..308b90aa4 100644 --- a/spec/operations/save_operation_spec.cr +++ b/spec/operations/save_operation_spec.cr @@ -87,7 +87,34 @@ private class AllowBlankComment < Comment::SaveOperation end end +module DefaultUserValidations + macro included + property starts_nil : String? = nil + + default_validations do + self.starts_nil = self.starts_nil.to_s + "!" + validate_required nickname + end + end +end + +private class UserWithDefaultValidations < User::SaveOperation + include DefaultUserValidations + + before_save do + self.starts_nil = "not nil" + end +end + describe "Avram::SaveOperation" do + it "calls the default validations after the before_save" do + UserWithDefaultValidations.create(name: "TestName", nickname: "TestNickname", joined_at: Time.utc, age: 400) do |op, u| + op.starts_nil.should eq("not nil!") + u.should_not be_nil + u.not_nil!.nickname.should eq("TestNickname") + end + end + it "allows overriding the param_key" do ParamKeySaveOperation.param_key.should eq "custom_param" end diff --git a/spec/polymorphic_spec.cr b/spec/polymorphic_spec.cr index 340619243..8db0218ad 100644 --- a/spec/polymorphic_spec.cr +++ b/spec/polymorphic_spec.cr @@ -30,7 +30,21 @@ private class OptionalPolymorphicEvent < BaseModel end end +class TestPolymorphicSave < PolymorphicEvent::SaveOperation + before_save do + task = PolymorphicTask::SaveOperation.create!(title: "Use Lucky") + task_id.value = task.id + end +end + describe "polymorphic belongs to" do + it "allows you to set the association before save" do + TestPolymorphicSave.create do |op, tp| + op.valid?.should eq(true) + tp.should_not be_nil + end + end + it "sets up a method for accessing associated record" do task = PolymorphicTask::SaveOperation.create!(title: "Use Lucky") event = PolymorphicEvent::SaveOperation.create!(task_id: task.id) diff --git a/src/avram/delete_operation.cr b/src/avram/delete_operation.cr index 6855e36dd..0fbbfd12a 100644 --- a/src/avram/delete_operation.cr +++ b/src/avram/delete_operation.cr @@ -73,9 +73,13 @@ abstract class Avram::DeleteOperation(T) end end + # :nodoc: + def default_validations; end + # Returns `true` if all attributes are valid, # and there's no custom errors def valid? + default_validations custom_errors.empty? && attributes.all?(&.valid?) end diff --git a/src/avram/operation.cr b/src/avram/operation.cr index 922631223..acfe0886b 100644 --- a/src/avram/operation.cr +++ b/src/avram/operation.cr @@ -92,9 +92,13 @@ abstract class Avram::Operation @params = Avram::Params.new end + # :nodoc: + def default_validations; end + # Returns `true` if all attributes are valid, # and there's no custom errors def valid? + default_validations custom_errors.empty? && attributes.all?(&.valid?) end diff --git a/src/avram/polymorphic.cr b/src/avram/polymorphic.cr index 7ccffc86f..3c996f577 100644 --- a/src/avram/polymorphic.cr +++ b/src/avram/polymorphic.cr @@ -140,7 +140,10 @@ module Avram::Polymorphic macro finished class SaveOperation - before_save do + # These validations must be run after all of the `before_save` + # in case anyone sets their polymorphic association in a callback. + # These are ran in the SaveOperation#valid? method + default_validations do {% list_of_foreign_keys = associations.map(&.id).map { |assoc| "#{assoc.id}_id".id } %} # TODO: Needs to actually get the foreign key from the ASSOCIATIONS constant diff --git a/src/avram/save_operation.cr b/src/avram/save_operation.cr index b752ce20f..881919449 100644 --- a/src/avram/save_operation.cr +++ b/src/avram/save_operation.cr @@ -206,10 +206,17 @@ abstract class Avram::SaveOperation(T) end end + # Runs all required validations for required types + # as well as any additional valitaions the type needs to run + # e.g. polymorphic validations def run_default_validations validate_required *required_attributes + default_validations end + # :nodoc: + def default_validations; end + # This allows you to skip the default validations # which may be used as an escape hatch when you want # to allow storing an empty string value. diff --git a/src/avram/validations.cr b/src/avram/validations.cr index 0b4137e05..dea0342e5 100644 --- a/src/avram/validations.cr +++ b/src/avram/validations.cr @@ -7,6 +7,31 @@ require "./validations/callable_error_message" module Avram::Validations extend self + macro included + abstract def default_validations + end + + # Defines an instance method that gets called + # during validation of an operation. Define your default + # validations inside of the block. + # ``` + # default_validations do + # validate_required some_attribute + # end + # ``` + macro default_validations + # :nodoc: + def default_validations + {% if @type.methods.map(&.name).includes?(:default_validations.id) %} + previous_def + {% else %} + super + {% end %} + + {{ yield }} + end + end + # Validates that at most one attribute is filled # # If more than one attribute is filled it will mark all but the first filled