toolchain 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +5 -0
- data/.rspec +1 -0
- data/Gemfile +10 -0
- data/Gemfile.lock +34 -0
- data/LICENSE +22 -0
- data/README.md +254 -0
- data/Rakefile +1 -0
- data/lib/toolchain.rb +2 -0
- data/lib/toolchain/attributes.rb +154 -0
- data/lib/toolchain/attributes/configuration.rb +41 -0
- data/lib/toolchain/attributes/errors.rb +5 -0
- data/lib/toolchain/attributes/errors/invalid_hash_transformation.rb +4 -0
- data/lib/toolchain/attributes/errors/invalid_mass_assignment.rb +4 -0
- data/lib/toolchain/attributes/errors/type_mismatch.rb +4 -0
- data/lib/toolchain/attributes/ext/boolean.rb +4 -0
- data/lib/toolchain/attributes/helpers.rb +72 -0
- data/lib/toolchain/validations.rb +103 -0
- data/lib/toolchain/validations/delegation.rb +31 -0
- data/lib/toolchain/validations/delegator.rb +51 -0
- data/lib/toolchain/validations/helpers.rb +58 -0
- data/lib/toolchain/validations/validation_errors.rb +82 -0
- data/lib/toolchain/validations/validators.rb +12 -0
- data/lib/toolchain/validations/validators/acceptance.rb +25 -0
- data/lib/toolchain/validations/validators/base.rb +54 -0
- data/lib/toolchain/validations/validators/confirmation.rb +39 -0
- data/lib/toolchain/validations/validators/email.rb +26 -0
- data/lib/toolchain/validations/validators/exclusion.rb +29 -0
- data/lib/toolchain/validations/validators/format.rb +25 -0
- data/lib/toolchain/validations/validators/inclusion.rb +29 -0
- data/lib/toolchain/validations/validators/length.rb +60 -0
- data/lib/toolchain/validations/validators/presence.rb +25 -0
- data/lib/toolchain/version.rb +3 -0
- data/spec/spec_helper.rb +11 -0
- data/spec/toolchain/attributes/attribute_spec.rb +47 -0
- data/spec/toolchain/attributes/attributes_spec.rb +193 -0
- data/spec/toolchain/attributes/base_helper.rb +37 -0
- data/spec/toolchain/attributes/boolean_spec.rb +38 -0
- data/spec/toolchain/attributes/configuration_spec.rb +33 -0
- data/spec/toolchain/attributes/date_time_spec.rb +21 -0
- data/spec/toolchain/attributes/hash_spec.rb +61 -0
- data/spec/toolchain/attributes/include_attributes_spec.rb +19 -0
- data/spec/toolchain/attributes/initializer_spec.rb +32 -0
- data/spec/toolchain/validations/base_helper.rb +2 -0
- data/spec/toolchain/validations/custom_validations_spec.rb +26 -0
- data/spec/toolchain/validations/delegation_spec.rb +70 -0
- data/spec/toolchain/validations/include_validations_spec.rb +20 -0
- data/spec/toolchain/validations/inheritence_spec.rb +26 -0
- data/spec/toolchain/validations/multiple_validations_spec.rb +19 -0
- data/spec/toolchain/validations/nested_validations_spec.rb +68 -0
- data/spec/toolchain/validations/validation_errors_spec.rb +9 -0
- data/spec/toolchain/validations/validators/acceptance_spec.rb +48 -0
- data/spec/toolchain/validations/validators/confirmation_spec.rb +86 -0
- data/spec/toolchain/validations/validators/email_spec.rb +41 -0
- data/spec/toolchain/validations/validators/exclusion_spec.rb +53 -0
- data/spec/toolchain/validations/validators/format_spec.rb +44 -0
- data/spec/toolchain/validations/validators/inclusion_spec.rb +50 -0
- data/spec/toolchain/validations/validators/length_spec.rb +134 -0
- data/spec/toolchain/validations/validators/presence_spec.rb +34 -0
- data/toolchain.gemspec +21 -0
- metadata +161 -0
@@ -0,0 +1,37 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "#{TOOLCHAIN_ROOT}/attributes"
|
3
|
+
|
4
|
+
require "time"
|
5
|
+
|
6
|
+
class ModelData
|
7
|
+
include Toolchain::Attributes
|
8
|
+
|
9
|
+
attribute :string, String
|
10
|
+
attribute :string_default, String, ""
|
11
|
+
attribute :integer, Integer
|
12
|
+
attribute :integer_default, Integer, 0
|
13
|
+
attribute :float, Float
|
14
|
+
attribute :float_default, Float, 0.0
|
15
|
+
attribute :array, Array
|
16
|
+
attribute :array_default, Array, []
|
17
|
+
attribute :hash, Hash
|
18
|
+
attribute :hash_default, Hash, {}
|
19
|
+
attribute :boolean, Boolean
|
20
|
+
attribute :boolean_default, Boolean, false
|
21
|
+
attribute :time, Time
|
22
|
+
attribute :time_default, Time, -> { Time.now }
|
23
|
+
attribute :date, Date
|
24
|
+
attribute :date_default, Date, -> { Date.today }
|
25
|
+
attribute :date_time, DateTime
|
26
|
+
attribute :date_time_default, DateTime, -> { DateTime.now }
|
27
|
+
end
|
28
|
+
|
29
|
+
RSpec.configure do |config|
|
30
|
+
|
31
|
+
config.before do
|
32
|
+
Toolchain::Attributes::Configuration.configure do |config|
|
33
|
+
config.hash_transformation = :symbolize_keys
|
34
|
+
config.include_nil_in_attributes = false
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require_relative "base_helper"
|
2
|
+
|
3
|
+
describe Toolchain::Attributes, "Boolean" do
|
4
|
+
|
5
|
+
let(:model) { ModelData.new }
|
6
|
+
|
7
|
+
it "should set the boolean to true" do
|
8
|
+
expect { model.boolean = true }.
|
9
|
+
to change { model.boolean }.from(nil).to(true)
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should accept '1' as true" do
|
13
|
+
expect { model.boolean = "1" }.
|
14
|
+
to change { model.boolean }.from(nil).to(true)
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should accept 1 as true" do
|
18
|
+
expect { model.boolean = 1 }.
|
19
|
+
to change { model.boolean }.from(nil).to(true)
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should accept '0' as false" do
|
23
|
+
expect { model.boolean = '0' }.
|
24
|
+
to change { model.boolean }.from(nil).to(false)
|
25
|
+
end
|
26
|
+
it "should accept 0 as false" do
|
27
|
+
expect { model.boolean = 0 }.
|
28
|
+
to change { model.boolean }.from(nil).to(false)
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should raise exception unless type is TrueClass or FalseClass" do
|
32
|
+
expect { model.boolean = "Example" }.
|
33
|
+
to raise_error(
|
34
|
+
Toolchain::Attributes::Errors::TypeMismatch,
|
35
|
+
"ModelData#boolean expected [TrueClass, FalseClass] type, " +
|
36
|
+
"but received String (Example).")
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require_relative "base_helper"
|
2
|
+
|
3
|
+
describe Toolchain::Attributes::Configuration do
|
4
|
+
|
5
|
+
let(:config) { Toolchain::Attributes::Configuration }
|
6
|
+
|
7
|
+
it "should configure for symbol" do
|
8
|
+
config.hash_transformation.should == :symbolize_keys
|
9
|
+
end
|
10
|
+
|
11
|
+
it "should configure for string" do
|
12
|
+
expect { config.hash_transformation = :stringify_keys }.
|
13
|
+
to change { config.hash_transformation }.
|
14
|
+
from(:symbolize_keys).to(:stringify_keys)
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should raise an exception on invalid type" do
|
18
|
+
expect { config.configure { |c| c.hash_transformation = :foo } }.
|
19
|
+
to raise_error(
|
20
|
+
Toolchain::Attributes::Errors::InvalidHashTransformation,
|
21
|
+
"valid types: :symbolize_keys, :stringify_keys."
|
22
|
+
)
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should configure to omit nil values in attributes" do
|
26
|
+
config.include_nil_in_attributes.should == false
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should configure to omit nil values in attributes" do
|
30
|
+
expect { config.configure { |c| c.include_nil_in_attributes = true } }.
|
31
|
+
to change { config.include_nil_in_attributes }.from(false).to(true)
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require_relative "base_helper"
|
2
|
+
|
3
|
+
describe Toolchain::Attributes, "Date, Time, DateTime" do
|
4
|
+
|
5
|
+
let(:model) { ModelData.new }
|
6
|
+
|
7
|
+
it "should parse the time if string" do
|
8
|
+
expect { model.time = Time.new(2000).to_s }.
|
9
|
+
to change { model.time }.from(nil).to(Time.new(2000))
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should parse the date if string" do
|
13
|
+
expect { model.date = Date.new(2000).to_s }.
|
14
|
+
to change { model.date }.from(nil).to(Date.new(2000))
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should parse the date_time if string" do
|
18
|
+
expect { model.date_time = DateTime.new(2000).to_s }.
|
19
|
+
to change { model.date_time }.from(nil).to(DateTime.new(2000))
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require_relative "base_helper"
|
2
|
+
|
3
|
+
describe Toolchain::Attributes, "Hash" do
|
4
|
+
|
5
|
+
let(:model) { ModelData.new }
|
6
|
+
|
7
|
+
it "should symbolize keys" do
|
8
|
+
model.hash = {
|
9
|
+
"foo" => "bar",
|
10
|
+
baz: true,
|
11
|
+
"deep" => {
|
12
|
+
nested: "hash",
|
13
|
+
"become" => "symbolized",
|
14
|
+
once: {
|
15
|
+
"more" => :is_enough
|
16
|
+
}
|
17
|
+
}
|
18
|
+
}
|
19
|
+
|
20
|
+
model.hash.should == {
|
21
|
+
foo: "bar",
|
22
|
+
baz: true,
|
23
|
+
deep: {
|
24
|
+
nested: "hash",
|
25
|
+
become: "symbolized",
|
26
|
+
once: {
|
27
|
+
more: :is_enough
|
28
|
+
}
|
29
|
+
}
|
30
|
+
}
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should stringify keys" do
|
34
|
+
Toolchain::Attributes::Configuration.
|
35
|
+
hash_transformation = :stringify_keys
|
36
|
+
|
37
|
+
model.hash = {
|
38
|
+
"foo" => "bar",
|
39
|
+
baz: true,
|
40
|
+
"deep" => {
|
41
|
+
nested: "hash",
|
42
|
+
"become" => "symbolized",
|
43
|
+
once: {
|
44
|
+
"more" => :is_enough
|
45
|
+
}
|
46
|
+
}
|
47
|
+
}
|
48
|
+
|
49
|
+
model.hash.should == {
|
50
|
+
"foo" => "bar",
|
51
|
+
"baz" => true,
|
52
|
+
"deep" => {
|
53
|
+
"nested" => "hash",
|
54
|
+
"become" => "symbolized",
|
55
|
+
"once" => {
|
56
|
+
"more" => :is_enough
|
57
|
+
}
|
58
|
+
}
|
59
|
+
}
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require_relative "base_helper"
|
2
|
+
|
3
|
+
describe Toolchain::Attributes, "includeAttributes" do
|
4
|
+
|
5
|
+
UserExternalAttributes = Proc.new do
|
6
|
+
attribute :name, String
|
7
|
+
end
|
8
|
+
|
9
|
+
class UserIncludedAttributes
|
10
|
+
include Toolchain::Attributes
|
11
|
+
include_attributes UserExternalAttributes
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should include validations" do
|
15
|
+
user = UserIncludedAttributes.new
|
16
|
+
user.name = "Michael"
|
17
|
+
user.name.should == "Michael"
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require_relative "base_helper"
|
2
|
+
|
3
|
+
describe Toolchain::Attributes, "Initializer" do
|
4
|
+
|
5
|
+
it "should initialize with attributes" do
|
6
|
+
date, time, date_time = Date.today, Time.now, DateTime.now
|
7
|
+
|
8
|
+
model = ModelData.new(
|
9
|
+
array_default: [1, 2, 3],
|
10
|
+
boolean_default: true,
|
11
|
+
float_default: 1.23,
|
12
|
+
hash_default: {foo: :bar},
|
13
|
+
integer_default: 123,
|
14
|
+
string_default: "value",
|
15
|
+
date_default: date,
|
16
|
+
time_default: time,
|
17
|
+
date_time_default: date_time
|
18
|
+
)
|
19
|
+
|
20
|
+
model.attributes.should == {
|
21
|
+
array_default: [1, 2, 3],
|
22
|
+
boolean_default: true,
|
23
|
+
float_default: 1.23,
|
24
|
+
hash_default: {foo: :bar},
|
25
|
+
integer_default: 123,
|
26
|
+
string_default: "value",
|
27
|
+
date_default: date,
|
28
|
+
time_default: time,
|
29
|
+
date_time_default: date_time
|
30
|
+
}
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require_relative "base_helper"
|
2
|
+
|
3
|
+
describe Toolchain::Validations, "Custom Validations" do
|
4
|
+
|
5
|
+
class UserCustomValidation
|
6
|
+
include Toolchain::Validations
|
7
|
+
attr_accessor :name
|
8
|
+
attr_accessor :error_out
|
9
|
+
validate :name_validation
|
10
|
+
|
11
|
+
def name_validation
|
12
|
+
errors.add(:name, "custom validation error") if error_out
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should be valid" do
|
17
|
+
UserCustomValidation.new.should be_valid
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should be invalid" do
|
21
|
+
user = UserCustomValidation.new
|
22
|
+
user.error_out = true
|
23
|
+
user.should_not be_valid
|
24
|
+
user.errors.should == { name: ["custom validation error"] }
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require_relative "base_helper"
|
2
|
+
|
3
|
+
describe Toolchain::Validations, "Delegation" do
|
4
|
+
|
5
|
+
module PersonAttributes
|
6
|
+
attr_accessor :name, :email, :terms, :new
|
7
|
+
|
8
|
+
def keys
|
9
|
+
[:name, :email, :terms, :new]
|
10
|
+
end
|
11
|
+
|
12
|
+
def attributes
|
13
|
+
{ name: name, email: email, terms: terms, new: new }
|
14
|
+
end
|
15
|
+
|
16
|
+
def attributes=(attrs)
|
17
|
+
attrs.each do |key, value|
|
18
|
+
send("#{key}=", value) if keys.include?(key)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class BasePersonValidator
|
24
|
+
include PersonAttributes
|
25
|
+
include Toolchain::Validations
|
26
|
+
include Toolchain::Validations::Delegation
|
27
|
+
validates :name, presence: true
|
28
|
+
validates :email, email: true
|
29
|
+
end
|
30
|
+
|
31
|
+
class NewPersonValidator < BasePersonValidator
|
32
|
+
validates :terms, acceptance: true
|
33
|
+
end
|
34
|
+
|
35
|
+
class ExistingPersonValidator < BasePersonValidator
|
36
|
+
end
|
37
|
+
|
38
|
+
class PersonDelegatorValidation
|
39
|
+
include PersonAttributes
|
40
|
+
include Toolchain::Validations::Delegator
|
41
|
+
|
42
|
+
validate_with do |i|
|
43
|
+
if i.new
|
44
|
+
NewPersonValidator
|
45
|
+
else
|
46
|
+
ExistingPersonValidator
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
it "should validate name, email, terms" do
|
52
|
+
person = PersonDelegatorValidation.new
|
53
|
+
person.new = true
|
54
|
+
person.should_not be_valid
|
55
|
+
person.errors.to_hash.should == {
|
56
|
+
name: ["can't be blank"],
|
57
|
+
email: ["is invalid"],
|
58
|
+
terms: ["must be accepted"]
|
59
|
+
}
|
60
|
+
end
|
61
|
+
|
62
|
+
it "should validate name, email" do
|
63
|
+
person = PersonDelegatorValidation.new
|
64
|
+
person.should_not be_valid
|
65
|
+
person.errors.to_hash.should == {
|
66
|
+
name: ["can't be blank"],
|
67
|
+
email: ["is invalid"]
|
68
|
+
}
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require_relative "base_helper"
|
2
|
+
|
3
|
+
describe Toolchain::Validations, "IncludeValidations" do
|
4
|
+
|
5
|
+
UserExternalValidations = Proc.new do
|
6
|
+
validates :name, presence: true
|
7
|
+
end
|
8
|
+
|
9
|
+
class UserIncludedValidations
|
10
|
+
include Toolchain::Validations
|
11
|
+
include_validations UserExternalValidations
|
12
|
+
attr_accessor :name
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should include validations" do
|
16
|
+
user = UserIncludedValidations.new
|
17
|
+
user.should_not be_valid
|
18
|
+
user.errors[:name].should == ["can't be blank"]
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require_relative "base_helper"
|
2
|
+
|
3
|
+
describe Toolchain::Validations, "Inheritence" do
|
4
|
+
|
5
|
+
class UserInheritingValidations
|
6
|
+
include Toolchain::Validations
|
7
|
+
attr_reader :name
|
8
|
+
validates :name, presence: true
|
9
|
+
validate :name_validation
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def name_validation
|
14
|
+
errors.add(:name, "custom name error")
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class UserInheritedValidations < UserInheritingValidations
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should be invalid" do
|
22
|
+
user = UserInheritedValidations.new
|
23
|
+
user.should_not be_valid
|
24
|
+
user.errors.should == { name: ["can't be blank", "custom name error"] }
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require_relative "base_helper"
|
2
|
+
|
3
|
+
describe Toolchain::Validations, "Multiple Validations" do
|
4
|
+
|
5
|
+
class UserMultipleValidator
|
6
|
+
include Toolchain::Validations
|
7
|
+
attr_accessor :email
|
8
|
+
validates :email,
|
9
|
+
presence: { message: "provide an email address" },
|
10
|
+
email: { message: "email address isn't valid" }
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should use multiple validators" do
|
14
|
+
user = UserMultipleValidator.new
|
15
|
+
user.should_not be_valid
|
16
|
+
user.errors[:email].should ==
|
17
|
+
["provide an email address", "email address isn't valid"]
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require_relative "base_helper"
|
2
|
+
|
3
|
+
describe Toolchain::Validations, "Nested Validation" do
|
4
|
+
|
5
|
+
class UserNestedValidation
|
6
|
+
include Toolchain::Validations
|
7
|
+
attr_accessor :config
|
8
|
+
validates :config, :name, presence: true
|
9
|
+
validates :config, :email, email: true
|
10
|
+
end
|
11
|
+
|
12
|
+
let(:user) { UserNestedValidation.new }
|
13
|
+
|
14
|
+
it "should be valid" do
|
15
|
+
user.config = {
|
16
|
+
name: "Example",
|
17
|
+
email: "example@machinery.io"
|
18
|
+
}
|
19
|
+
user.should be_valid
|
20
|
+
end
|
21
|
+
|
22
|
+
it "is invalid if nil" do
|
23
|
+
user.should_not be_valid
|
24
|
+
user.errors[:config][:name].should == ["can't be blank"]
|
25
|
+
user.errors[:config][:email].should == ["is invalid"]
|
26
|
+
end
|
27
|
+
|
28
|
+
class UserDeeplyNestedValidation
|
29
|
+
include Toolchain::Validations
|
30
|
+
attr_accessor :config
|
31
|
+
validates :config, :deep, :name, presence: true
|
32
|
+
validates :config, :deep, :email, email: true
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should be valid" do
|
36
|
+
user = UserDeeplyNestedValidation.new
|
37
|
+
user.config = {
|
38
|
+
deep: {
|
39
|
+
name: "Example",
|
40
|
+
email: "example@machinery.io"
|
41
|
+
}
|
42
|
+
}
|
43
|
+
user.should be_valid
|
44
|
+
end
|
45
|
+
|
46
|
+
it "is invalid if nil" do
|
47
|
+
user = UserDeeplyNestedValidation.new
|
48
|
+
user.config = {}
|
49
|
+
user.should_not be_valid
|
50
|
+
user.errors[:config][:deep][:name].should == ["can't be blank"]
|
51
|
+
end
|
52
|
+
|
53
|
+
class UserCustomNestedValidation
|
54
|
+
include Toolchain::Validations
|
55
|
+
attr_accessor :config
|
56
|
+
validate :name_validation
|
57
|
+
|
58
|
+
def name_validation
|
59
|
+
errors.add(:config, :name, "custom validation error")
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should be invalid" do
|
64
|
+
user = UserCustomNestedValidation.new
|
65
|
+
user.should_not be_valid
|
66
|
+
user.errors.should == { config: { name: ["custom validation error"] } }
|
67
|
+
end
|
68
|
+
end
|