axiom-types 0.0.1

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.
Files changed (63) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +37 -0
  3. data/.rspec +4 -0
  4. data/.rvmrc +1 -0
  5. data/.travis.yml +35 -0
  6. data/.yardopts +4 -0
  7. data/Gemfile +10 -0
  8. data/Gemfile.devtools +62 -0
  9. data/Guardfile +24 -0
  10. data/LICENSE +20 -0
  11. data/README.md +76 -0
  12. data/Rakefile +6 -0
  13. data/TODO +25 -0
  14. data/axiom-types.gemspec +26 -0
  15. data/config/flay.yml +3 -0
  16. data/config/flog.yml +2 -0
  17. data/config/mutant.yml +3 -0
  18. data/config/reek.yml +100 -0
  19. data/config/roodi.yml +16 -0
  20. data/config/yardstick.yml +2 -0
  21. data/lib/axiom-types.rb +3 -0
  22. data/lib/axiom/types.rb +65 -0
  23. data/lib/axiom/types/array.rb +13 -0
  24. data/lib/axiom/types/boolean.rb +14 -0
  25. data/lib/axiom/types/class.rb +13 -0
  26. data/lib/axiom/types/date.rb +18 -0
  27. data/lib/axiom/types/date_time.rb +21 -0
  28. data/lib/axiom/types/decimal.rb +13 -0
  29. data/lib/axiom/types/encodable.rb +75 -0
  30. data/lib/axiom/types/float.rb +13 -0
  31. data/lib/axiom/types/hash.rb +45 -0
  32. data/lib/axiom/types/integer.rb +13 -0
  33. data/lib/axiom/types/length_comparable.rb +51 -0
  34. data/lib/axiom/types/numeric.rb +13 -0
  35. data/lib/axiom/types/object.rb +36 -0
  36. data/lib/axiom/types/set.rb +13 -0
  37. data/lib/axiom/types/string.rb +18 -0
  38. data/lib/axiom/types/support/options.rb +129 -0
  39. data/lib/axiom/types/symbol.rb +18 -0
  40. data/lib/axiom/types/time.rb +22 -0
  41. data/lib/axiom/types/type.rb +136 -0
  42. data/lib/axiom/types/value_comparable.rb +49 -0
  43. data/lib/axiom/types/version.rb +10 -0
  44. data/spec/spec_helper.rb +37 -0
  45. data/spec/unit/axiom/types/class_methods/finalize_spec.rb +22 -0
  46. data/spec/unit/axiom/types/encodable/class_methods/extended_spec.rb +31 -0
  47. data/spec/unit/axiom/types/encodable/finalize_spec.rb +55 -0
  48. data/spec/unit/axiom/types/hash/class_methods/finalize_spec.rb +55 -0
  49. data/spec/unit/axiom/types/length_comparable/class_methods/extended_spec.rb +32 -0
  50. data/spec/unit/axiom/types/length_comparable/finalize_spec.rb +32 -0
  51. data/spec/unit/axiom/types/object/class_methods/coercion_method_spec.rb +27 -0
  52. data/spec/unit/axiom/types/object/class_methods/finalize_spec.rb +29 -0
  53. data/spec/unit/axiom/types/object/class_methods/primitive_spec.rb +27 -0
  54. data/spec/unit/axiom/types/options/accept_options_spec.rb +98 -0
  55. data/spec/unit/axiom/types/options/inherited_spec.rb +38 -0
  56. data/spec/unit/axiom/types/type/class_methods/constraint_spec.rb +32 -0
  57. data/spec/unit/axiom/types/type/class_methods/finalize_spec.rb +20 -0
  58. data/spec/unit/axiom/types/type/class_methods/include_predicate_spec.rb +25 -0
  59. data/spec/unit/axiom/types/type/class_methods/includes_spec.rb +45 -0
  60. data/spec/unit/axiom/types/type/class_methods/new_spec.rb +79 -0
  61. data/spec/unit/axiom/types/value_comparable/class_methods/extended_spec.rb +32 -0
  62. data/spec/unit/axiom/types/value_comparable/finalize_spec.rb +32 -0
  63. metadata +215 -0
@@ -0,0 +1,31 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Axiom::Types::Encodable, '.extended', :if => RUBY_VERSION >= '1.9' do
6
+ subject { object.extend(described_class) }
7
+
8
+ let(:object) { Class.new(Axiom::Types::Type) }
9
+
10
+ it 'delegates to the ancestor' do
11
+ # RSpec will reset stubbed methods after the example. A normal expectation
12
+ # causes a SystemStackError to be thrown, so we stub it first so that RSpec
13
+ # tracks the original method (if any), then we add our own stub that
14
+ # actually works, and finally when the example finishes RSpec will reset the
15
+ # Module#extended method back to it's original state.
16
+ Module.any_instance.stub(:extended).with(object)
17
+
18
+ delegated_ancestor = false
19
+ Module.send(:define_method, :extended) { |_| delegated_ancestor = true }
20
+ expect { subject }.to change { delegated_ancestor }.from(false).to(true)
21
+ end
22
+
23
+ it 'adds encoding method' do
24
+ expect { subject }.to change { object.respond_to?(:encoding) }.
25
+ from(false).to(true)
26
+ end
27
+
28
+ it 'sets the encoding default to UTF-8' do
29
+ expect(subject.encoding).to be(Encoding::UTF_8)
30
+ end
31
+ end
@@ -0,0 +1,55 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Axiom::Types::Encodable, '#finalize', :if => RUBY_VERSION >= '1.9' do
6
+ subject { object.finalize }
7
+
8
+ let(:object) { Class.new(Axiom::Types::Type).extend(described_class) }
9
+
10
+ context 'when an ascii compatible encoding (UTF-8) is used' do
11
+ it_should_behave_like 'a command method'
12
+ it_should_behave_like 'an idempotent method'
13
+
14
+ it { should be_frozen }
15
+
16
+ its(:constraint) { should be_frozen }
17
+
18
+ defined?(::Encoding) && Encoding.list.each do |encoding|
19
+ if encoding.ascii_compatible?
20
+ it "adds a constraint that returns true for #{encoding} encoding" do
21
+ should include(''.force_encoding(encoding))
22
+ end
23
+ else
24
+ it "adds a constraint that returns false for #{encoding} encoding" do
25
+ should_not include(''.force_encoding(encoding))
26
+ end
27
+ end
28
+ end
29
+ end
30
+
31
+ context 'when an non-ascii compatible encoding (UTF-16BE) is used' do
32
+ before do
33
+ object.encoding Encoding::UTF_16BE
34
+ end
35
+
36
+ it_should_behave_like 'a command method'
37
+ it_should_behave_like 'an idempotent method'
38
+
39
+ it { should be_frozen }
40
+
41
+ its(:constraint) { should be_frozen }
42
+
43
+ defined?(::Encoding) && Encoding.list.each do |encoding|
44
+ if encoding.equal?(Encoding::UTF_16BE)
45
+ it "adds a constraint that returns true for #{encoding} encoding" do
46
+ should include(''.force_encoding(encoding))
47
+ end
48
+ else
49
+ it "adds a constraint that returns false for #{encoding} encoding" do
50
+ should_not include(''.force_encoding(encoding))
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,55 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Axiom::Types::Hash, '.finalize' do
6
+ subject { object.finalize }
7
+
8
+ let(:object) { Class.new(Axiom::Types::Hash) }
9
+
10
+ context 'with the default key and value constraints' do
11
+ it_should_behave_like 'a command method'
12
+ it_should_behave_like 'an idempotent method'
13
+
14
+ it { should be_frozen }
15
+
16
+ its(:constraint) { should be_frozen }
17
+
18
+ it 'adds a constraint that returns true for a Hash' do
19
+ should include(Object.new => Object.new)
20
+ end
21
+
22
+ it 'adds a constraint that returns false for a non-Hash' do
23
+ should_not include(Object.new)
24
+ end
25
+ end
26
+
27
+ context 'with custom key and value constraints' do
28
+ let(:key) { :name }
29
+ let(:value) { 'Dan Kubb' }
30
+
31
+ before do
32
+ object.key_type Axiom::Types::Symbol
33
+ object.value_type Axiom::Types::String
34
+ end
35
+
36
+ it_should_behave_like 'a command method'
37
+ it_should_behave_like 'an idempotent method'
38
+
39
+ it { should be_frozen }
40
+
41
+ its(:constraint) { should be_frozen }
42
+
43
+ it 'adds a constraint that returns true for valid keys and values' do
44
+ should include(key => value)
45
+ end
46
+
47
+ it 'adds a constraint that returns false for an invalid key' do
48
+ should_not include(key.to_s => value)
49
+ end
50
+
51
+ it 'adds a constraint that returns false for an invalid value' do
52
+ should_not include(key => value.to_sym)
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,32 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Axiom::Types::LengthComparable, '.extended' do
6
+ subject { object.extend(described_class) }
7
+
8
+ let(:object) { Class.new(Axiom::Types::Type) }
9
+
10
+ it 'delegates to the ancestor' do
11
+ # RSpec will reset stubbed methods after the example. A normal expectation
12
+ # causes a SystemStackError to be thrown, so we stub it first so that RSpec
13
+ # tracks the original method (if any), then we add our own stub that
14
+ # actually works, and finally when the example finishes RSpec will reset the
15
+ # Module#extended method back to it's original state.
16
+ Module.any_instance.stub(:extended).with(object)
17
+
18
+ delegated_ancestor = false
19
+ Module.send(:define_method, :extended) { |_| delegated_ancestor = true }
20
+ expect { subject }.to change { delegated_ancestor }.from(false).to(true)
21
+ end
22
+
23
+ it 'adds minimum_length method' do
24
+ expect { subject }.to change { object.respond_to?(:minimum_length) }.
25
+ from(false).to(true)
26
+ end
27
+
28
+ it 'adds maxumum_length method' do
29
+ expect { subject }.to change { object.respond_to?(:maximum_length) }.
30
+ from(false).to(true)
31
+ end
32
+ end
@@ -0,0 +1,32 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Axiom::Types::LengthComparable, '#finalize' do
6
+ subject { object.finalize }
7
+
8
+ let(:object) do
9
+ Class.new(Axiom::Types::Type) do
10
+ extend Axiom::Types::LengthComparable
11
+ minimum_length 1
12
+ maximum_length 2
13
+ end
14
+ end
15
+
16
+ it_should_behave_like 'a command method'
17
+ it_should_behave_like 'an idempotent method'
18
+
19
+ it { should be_frozen }
20
+
21
+ its(:constraint) { should be_frozen }
22
+
23
+ it 'adds a constraint that returns true for a length within range' do
24
+ should include('a')
25
+ should include('ab')
26
+ end
27
+
28
+ it 'adds a constraint that returns false for a length not within range' do
29
+ should_not include('')
30
+ should_not include('abc')
31
+ end
32
+ end
@@ -0,0 +1,27 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Axiom::Types::Object, '.coercion_method' do
6
+ let(:object) { Class.new(described_class) }
7
+ let(:coercion_method) { :to_object }
8
+
9
+ context 'with no arguments' do
10
+ subject { object.coercion_method }
11
+
12
+ it { should be(coercion_method) }
13
+ end
14
+
15
+ context 'with a symbol' do
16
+ subject { object.coercion_method(symbol) }
17
+
18
+ let(:symbol) { :to_string }
19
+
20
+ it_should_behave_like 'a command method'
21
+
22
+ it 'sets the coercion_method' do
23
+ expect { subject }.to change { object.coercion_method }.
24
+ from(coercion_method).to(symbol)
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,29 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Axiom::Types::Object, '.finalize' do
6
+ subject { object.finalize }
7
+
8
+ let(:object) do
9
+ # Class.new { ... } does not work with this on 1.8.7
10
+ object = Class.new(described_class)
11
+ object.primitive(::String)
12
+ object
13
+ end
14
+
15
+ it_should_behave_like 'a command method'
16
+ it_should_behave_like 'an idempotent method'
17
+
18
+ it { should be_frozen }
19
+
20
+ its(:constraint) { should be_frozen }
21
+
22
+ it 'adds a constraint that returns true for a valid primitive' do
23
+ should include('string')
24
+ end
25
+
26
+ it 'adds a constraint that returns false for an invalid primitive' do
27
+ should_not include(nil)
28
+ end
29
+ end
@@ -0,0 +1,27 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Axiom::Types::Object, '.primitive' do
6
+ let(:object) { Class.new(described_class) }
7
+ let(:primitive) { RUBY_VERSION < '1.9' ? ::Object : ::BasicObject }
8
+
9
+ context 'with no arguments' do
10
+ subject { object.primitive }
11
+
12
+ it { should be(primitive) }
13
+ end
14
+
15
+ context 'with a class' do
16
+ subject { object.primitive(klass) }
17
+
18
+ let(:klass) { ::String }
19
+
20
+ it_should_behave_like 'a command method'
21
+
22
+ it 'sets the primitive' do
23
+ expect { subject }.to change { object.primitive.object_id }.
24
+ from(primitive.object_id).to(klass.object_id)
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,98 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Axiom::Types::Options, '#accept_options' do
6
+ subject { object.accept_options(*new_options) }
7
+
8
+ let(:object) { Class.new { extend Options, DescendantsTracker } }
9
+ let(:descendant) { Class.new(object) }
10
+
11
+ context 'with valid options' do
12
+ let(:new_options) { [ :primitive, :coerce_method ] }
13
+
14
+ it_should_behave_like 'a command method'
15
+ it_should_behave_like 'an idempotent method'
16
+
17
+ it 'adds methods to the object' do
18
+ expect(object).to_not respond_to(*new_options)
19
+ subject
20
+ expect(object).to respond_to(*new_options)
21
+ end
22
+
23
+ it 'adds methods to the object that can set a value' do
24
+ subject
25
+ object.primitive(::Symbol)
26
+ expect(object.primitive).to be(::Symbol)
27
+ end
28
+
29
+ context 'with the descendant class' do
30
+ let(:default) { ::String }
31
+
32
+ def force_inherit
33
+ descendant
34
+ end
35
+
36
+ context 'option added before inherited' do
37
+ before do
38
+ subject
39
+ expect(object.primitive(default)).to be(object)
40
+ force_inherit
41
+ end
42
+
43
+ it 'is idempotent' do
44
+ expect(descendant.accept_options(:primitive)).to be(descendant)
45
+ end
46
+
47
+ it 'adds the method to the descendants' do
48
+ expect(descendant).to respond_to(*new_options)
49
+ end
50
+
51
+ it 'sets the descendant defaults' do
52
+ expect(descendant.primitive).to be(default)
53
+ end
54
+
55
+ it 'adds methods to the descendant that can set a value' do
56
+ descendant.primitive(::Symbol)
57
+ expect(descendant.primitive).to be(::Symbol)
58
+ end
59
+ end
60
+
61
+ context 'option added after inherited' do
62
+ before do
63
+ force_inherit
64
+ subject
65
+ expect(object.primitive(default)).to be(object)
66
+ end
67
+
68
+ it 'is idempotent' do
69
+ expect(descendant.accept_options(:primitive)).to be(descendant)
70
+ end
71
+
72
+ it 'adds the method to the descendants' do
73
+ expect(descendant).to respond_to(*new_options)
74
+ end
75
+
76
+ it 'does not set the descendant defaults' do
77
+ expect(descendant.primitive).to be_nil
78
+ end
79
+
80
+ it 'adds methods to the descendant that can set a value' do
81
+ descendant.primitive(::Symbol)
82
+ expect(descendant.primitive).to be(::Symbol)
83
+ end
84
+ end
85
+ end
86
+ end
87
+
88
+ context 'with an option that conflicts with an existing method' do
89
+ let(:new_options) { [ :name ] }
90
+
91
+ specify do
92
+ expect { subject }.to raise_error(
93
+ Axiom::Types::Options::ReservedMethodError,
94
+ 'method named `:name` is already defined'
95
+ )
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,38 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Axiom::Types::Options, '#inherited' do
6
+ subject { descendant }
7
+
8
+ let(:object) do
9
+ Class.new(ancestor) do
10
+ extend Options, DescendantsTracker
11
+ accept_options :primitive, :coerce_method
12
+ primitive ::String
13
+ end
14
+ end
15
+
16
+ let(:ancestor) do
17
+ Class.new
18
+ end
19
+
20
+ let(:descendant) do
21
+ Class.new(object)
22
+ end
23
+
24
+ it 'delegates to the ancestor' do
25
+ ancestor.should_receive(:inherited).twice
26
+ subject
27
+ end
28
+
29
+ it 'adds the accepted option to the descendant' do
30
+ subject
31
+ expect(descendant).to respond_to(:primitive, :coerce_method)
32
+ end
33
+
34
+ it 'sets the default value for the descendant' do
35
+ subject
36
+ expect(descendant.primitive).to be(::String)
37
+ end
38
+ end
@@ -0,0 +1,32 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Axiom::Types::Type, '.constraint' do
6
+ let(:object) { Class.new(described_class) }
7
+
8
+ context 'with no arguments or block' do
9
+ subject { object.constraint }
10
+
11
+ it { should respond_to(:call) }
12
+ it { should be(Tautology) }
13
+ end
14
+
15
+ context 'with a callable object' do
16
+ subject { object.constraint(Contradiction) }
17
+
18
+ it_should_behave_like 'a command method'
19
+
20
+ its(:constraint) { should respond_to(:call) }
21
+ its(:constraint) { should_not be(Tautology) }
22
+ end
23
+
24
+ context 'with a block' do
25
+ subject { object.constraint { false } }
26
+
27
+ it_should_behave_like 'a command method'
28
+
29
+ its(:constraint) { should respond_to(:call) }
30
+ its(:constraint) { should_not be(Tautology) }
31
+ end
32
+ end