Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions spec/attribute_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -87,21 +87,21 @@ describe "Avram::Attribute" do
end

it "allows the from and to values to be nil" do
attribute = Avram::Attribute(String?).new(name: :color, param: nil, value: "teal", param_key: "test_form")
attribute = Avram::Attribute(String).new(name: :color, param: nil, value: "teal", param_key: "test_form")
attribute.value = nil
attribute.changed?(to: nil).should be_true

attribute = Avram::Attribute(String?).new(name: :color, param: nil, value: nil, param_key: "test_form")
attribute = Avram::Attribute(String).new(name: :color, param: nil, value: nil, param_key: "test_form")
attribute.value = "gold"
attribute.changed?(from: nil).should be_true
end

it "treats a blank string as nil" do
attribute = Avram::Attribute(String?).new(name: :color, param: nil, value: " ", param_key: "test_form")
attribute = Avram::Attribute(String).new(name: :color, param: nil, value: " ", param_key: "test_form")
attribute.value = "silver"
attribute.changed?(from: nil).should be_true

attribute = Avram::Attribute(String?).new(name: :color, param: nil, value: "purple", param_key: "test_form")
attribute = Avram::Attribute(String).new(name: :color, param: nil, value: "purple", param_key: "test_form")
attribute.value = " "
attribute.changed?(to: nil).should be_true
end
Expand Down
12 changes: 6 additions & 6 deletions spec/operations/define_attribute_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -51,15 +51,15 @@ end

describe "attribute in operations" do
it "is a PermittedAttribute" do
operation.title.should be_a(Avram::PermittedAttribute(String?))
operation.title.should be_a(Avram::PermittedAttribute(String))
operation.title.name.should eq(:title)
operation.title.param_key.should eq("data")

save_operation.password_confirmation.should be_a(Avram::PermittedAttribute(String?))
save_operation.password_confirmation.should be_a(Avram::PermittedAttribute(String))
save_operation.password_confirmation.name.should eq(:password_confirmation)
save_operation.password_confirmation.param_key.should eq("post")

delete_operation.accept_delete.should be_a(Avram::PermittedAttribute(Bool?))
delete_operation.accept_delete.should be_a(Avram::PermittedAttribute(Bool))
delete_operation.accept_delete.name.should eq(:accept_delete)
delete_operation.accept_delete.param_key.should eq("post")
end
Expand Down Expand Up @@ -181,15 +181,15 @@ end

describe "file_attribute in operation" do
it "is a PermittedAttribute" do
operation.thumb.should be_a(Avram::PermittedAttribute(Avram::Uploadable?))
operation.thumb.should be_a(Avram::PermittedAttribute(Avram::Uploadable))
operation.thumb.name.should eq(:thumb)
operation.thumb.param_key.should eq("data")

save_operation.thumb.should be_a(Avram::PermittedAttribute(Avram::Uploadable?))
save_operation.thumb.should be_a(Avram::PermittedAttribute(Avram::Uploadable))
save_operation.thumb.name.should eq(:thumb)
save_operation.thumb.param_key.should eq("post")

delete_operation.biometric_confirmation.should be_a(Avram::PermittedAttribute(Avram::Uploadable?))
delete_operation.biometric_confirmation.should be_a(Avram::PermittedAttribute(Avram::Uploadable))
delete_operation.biometric_confirmation.name.should eq(:biometric_confirmation)
delete_operation.biometric_confirmation.param_key.should eq("post")
end
Expand Down
24 changes: 15 additions & 9 deletions spec/validations_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ private def attribute(value : T) : Avram::Attribute(T) forall T
Avram::Attribute.new(value: value, param: nil, param_key: "fake", name: :fake)
end

private def nil_attribute(type : T.class) : Avram::Attribute(T) forall T
Avram::Attribute(T).new(value: nil, param: nil, param_key: "fake", name: :fake)
end

describe Avram::Validations do
describe "validate_at_most_one_filled" do
it "marks filled attribute as invalid if more than one is filled" do
Expand Down Expand Up @@ -52,7 +56,7 @@ describe Avram::Validations do
end

it "marks first field as invalid if no attributes are filled" do
first_blank_attribute = attribute(nil)
first_blank_attribute = nil_attribute(String)
second_blank_attribute = attribute("")

Avram::Validations.validate_exactly_one_filled(first_blank_attribute, second_blank_attribute)
Expand All @@ -75,7 +79,7 @@ describe Avram::Validations do
describe "validate_required" do
it "validates multiple attributes" do
empty_attribute = attribute("")
nil_attribute = attribute(nil)
nil_attribute = nil_attribute(String)

Avram::Validations.validate_required(empty_attribute, nil_attribute)

Expand Down Expand Up @@ -172,7 +176,7 @@ describe Avram::Validations do
Avram::Validations.validate_acceptance_of false_attribute
false_attribute.errors.should eq(["must be accepted"])

nil_attribute = attribute(nil)
nil_attribute = nil_attribute(Bool)
Avram::Validations.validate_acceptance_of nil_attribute
nil_attribute.errors.should eq(["must be accepted"])

Expand Down Expand Up @@ -208,7 +212,7 @@ describe Avram::Validations do
end

it "can allow nil" do
nil_name = Avram::Attribute(String?).new(value: nil, param: nil, param_key: "fake", name: :fake)
nil_name = Avram::Attribute(String).new(value: nil, param: nil, param_key: "fake", name: :fake)

Avram::Validations.validate_inclusion_of(nil_name, in: ["Jamie"], allow_nil: true)
nil_name.valid?.should be_true
Expand Down Expand Up @@ -241,23 +245,25 @@ describe Avram::Validations do
end

it "raises an error for an impossible condition" do
does_not_matter = attribute(nil)
expect_raises(Avram::ImpossibleValidation) do
Avram::Validations.validate_size_of does_not_matter, min: 4, max: 1
Avram::Validations.validate_size_of nil_attribute(String), min: 4, max: 1
end
end

it "can allow nil" do
just_nil = attribute(nil)
just_nil = nil_attribute(String)
Avram::Validations.validate_size_of(just_nil, is: 10, allow_nil: true)
just_nil.valid?.should be_true

just_nil = nil_attribute(String)
Avram::Validations.validate_size_of(just_nil, min: 1, max: 2, allow_nil: true)
just_nil.valid?.should be_true

just_nil = nil_attribute(String)
Avram::Validations.validate_size_of(just_nil, is: 10)
just_nil.valid?.should be_false

just_nil = nil_attribute(String)
Avram::Validations.validate_size_of(just_nil, min: 1, max: 2)
just_nil.valid?.should be_false
end
Expand Down Expand Up @@ -285,11 +291,11 @@ describe Avram::Validations do
end

it "can allow nil" do
just_nil = attribute(nil)
just_nil = nil_attribute(Int32)
Avram::Validations.validate_numeric(just_nil, greater_than: 1, less_than: 2, allow_nil: true)
just_nil.valid?.should be_true

just_nil = attribute(nil)
just_nil = nil_attribute(Int32)
Avram::Validations.validate_numeric(just_nil, greater_than: 1, less_than: 2)
just_nil.valid?.should be_false
end
Expand Down
40 changes: 16 additions & 24 deletions src/avram/attribute.cr
Original file line number Diff line number Diff line change
@@ -1,77 +1,69 @@
class Avram::Attribute(T)
alias ErrorMessage = String | Proc(String, String, String) | Avram::CallableErrorMessage
getter :name
setter :value
getter :param_key
getter name : Symbol
setter value : T?
getter param_key : String
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I change symbol to type declaration out of preference, hopefully that's ok 😄

@errors = [] of String
@param : Avram::Uploadable | String?

def initialize(
@name : Symbol,
@param : Avram::Uploadable | String?,
@value : T,
@param_key : String
)
def initialize(@name, @param, @value : T?, @param_key)
@original_value = @value
end

@_permitted : Avram::PermittedAttribute(T)?

def permitted
@_permitted ||= begin
Avram::PermittedAttribute.new(name: @name, param: @param, value: @value, param_key: @param_key).tap do |attribute|
Avram::PermittedAttribute(T).new(name: @name, param: @param, value: @value, param_key: @param_key).tap do |attribute|
errors.each do |error|
attribute.add_error error
end
end
end
end

def param
def param : Avram::Uploadable | String
@param || value.to_s
end

def add_error(message : ErrorMessage = "is invalid")
perform_add_error(message)
end

private def perform_add_error(message : String = "is invalid")
def add_error(message : String = "is invalid")
@errors << message
end

private def perform_add_error(message : (Proc | Avram::CallableErrorMessage))
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moving this overload into the add_error method simplifies the code and error stack traces

def add_error(message : (Proc | Avram::CallableErrorMessage))
message_string = message.call(@name.to_s, @value.to_s)
perform_add_error(message_string)
add_error(message_string)
end

def reset_errors
@errors = [] of String
end

def errors
def errors : Array(String)
@errors.uniq
end

def value
def value : T?
ensure_no_blank(@value)
end

def original_value
def original_value : T?
ensure_no_blank(@original_value)
end

private def ensure_no_blank(value : T)
private def ensure_no_blank(value : T?) : T?
if value.is_a?(Avram::Uploadable | String) && value.blank?
nil
else
value
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we should be adding a not_nil! here so the compiler knows this type is locked in? 🤔

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The value can always be nil, this code is just the logic that also treats blank strings as nil.

end
end

def valid?
def valid? : Bool
errors.empty?
end

def changed?(from : T | Nothing = Nothing.new, to : T | Nothing = Nothing.new)
def changed?(from : T? | Nothing = Nothing.new, to : T? | Nothing = Nothing.new) : Bool
from = from.is_a?(Nothing) ? true : from == original_value
to = to.is_a?(Nothing) ? true : to == value
value != original_value && from && to
Expand Down
4 changes: 2 additions & 2 deletions src/avram/define_attribute.cr
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ module Avram::DefineAttribute
type_declaration.value
end
%}
@_{{ name }} : Avram::Attribute({{ type }}?)?
@_{{ name }} : Avram::Attribute({{ type }})?

ensure_base_attributes_method_is_present

Expand All @@ -78,7 +78,7 @@ module Avram::DefineAttribute
end

private def _{{ name }}
@_{{ name }} ||= Avram::Attribute({{ type }}?).new(
@_{{ name }} ||= Avram::Attribute({{ type }}).new(
name: :{{ name }},
param: {{ name }}_param,
value: {{ default_value }},
Expand Down
4 changes: 2 additions & 2 deletions src/avram/delete_operation.cr
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ abstract class Avram::DeleteOperation(T)
end

{% for attribute in attributes %}
@_{{ attribute[:name] }} : Avram::Attribute({{ attribute[:type] }}?)?
@_{{ attribute[:name] }} : Avram::Attribute({{ attribute[:type] }})?

def {{ attribute[:name] }}
_{{ attribute[:name] }}
Expand All @@ -143,7 +143,7 @@ abstract class Avram::DeleteOperation(T)
record_value = @record.try(&.{{ attribute[:name] }})
value = record_value.nil? ? default_value_for_{{ attribute[:name] }} : record_value

@_{{ attribute[:name] }} ||= Avram::Attribute({{ attribute[:type] }}?).new(
@_{{ attribute[:name] }} ||= Avram::Attribute({{ attribute[:type] }}).new(
name: :{{ attribute[:name].id }},
param: permitted_params["{{ attribute[:name] }}"]?,
value: value,
Expand Down
4 changes: 2 additions & 2 deletions src/avram/save_operation.cr
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ abstract class Avram::SaveOperation(T)
end

{% for attribute in attributes %}
@_{{ attribute[:name] }} : Avram::Attribute({{ attribute[:type] }}?)?
@_{{ attribute[:name] }} : Avram::Attribute({{ attribute[:type] }})?

def {{ attribute[:name] }}
_{{ attribute[:name] }}
Expand All @@ -129,7 +129,7 @@ abstract class Avram::SaveOperation(T)
record_value = @record.try(&.{{ attribute[:name] }})
value = record_value.nil? ? default_value_for_{{ attribute[:name] }} : record_value

@_{{ attribute[:name] }} ||= Avram::Attribute({{ attribute[:type] }}?).new(
@_{{ attribute[:name] }} ||= Avram::Attribute({{ attribute[:type] }}).new(
name: :{{ attribute[:name].id }},
param: permitted_params["{{ attribute[:name] }}"]?,
value: value,
Expand Down
8 changes: 4 additions & 4 deletions src/avram/validations.cr
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ module Avram::Validations
#
# This validation is only for Boolean Attributes. The attribute will be marked
# as invalid for any value other than `true`.
def validate_acceptance_of(attribute : Avram::Attribute(Bool?), message : Avram::Attribute::ErrorMessage = "must be accepted")
def validate_acceptance_of(attribute : Avram::Attribute(Bool), message : Avram::Attribute::ErrorMessage = "must be accepted")
if attribute.value != true
attribute.add_error message
end
Expand Down Expand Up @@ -110,7 +110,7 @@ module Avram::Validations
# validate_size_of api_key, is: 32
# ```
def validate_size_of(
attribute : Avram::Attribute(String?),
attribute : Avram::Attribute(String),
*,
is exact_size,
message : Avram::Attribute::ErrorMessage = "is invalid",
Expand All @@ -128,7 +128,7 @@ module Avram::Validations
# validate_size_of password, min: 12
# ```
def validate_size_of(
attribute : Avram::Attribute(String?),
attribute : Avram::Attribute(String),
min = nil,
max = nil,
allow_nil : Bool = false
Expand Down Expand Up @@ -159,7 +159,7 @@ module Avram::Validations
# validate_numeric count, greater_than: 0, less_than: 1200
# ```
def validate_numeric(
attribute : Avram::Attribute(Number?),
attribute : Avram::Attribute(Number),
greater_than = nil,
less_than = nil,
allow_nil : Bool = false
Expand Down