lego 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +17 -0
- data/.rspec +2 -0
- data/Gemfile +13 -0
- data/Guardfile +5 -0
- data/LICENSE +22 -0
- data/README.md +3 -0
- data/Rakefile +7 -0
- data/lego.gemspec +21 -0
- data/lib/lego.rb +22 -0
- data/lib/lego/either.rb +91 -0
- data/lib/lego/model.rb +69 -0
- data/lib/lego/value.rb +15 -0
- data/lib/lego/value/base.rb +34 -0
- data/lib/lego/value/boolean.rb +26 -0
- data/lib/lego/value/date.rb +12 -0
- data/lib/lego/value/float.rb +22 -0
- data/lib/lego/value/integer.rb +22 -0
- data/lib/lego/value/set.rb +39 -0
- data/lib/lego/value/string.rb +20 -0
- data/spec/lego/either/fail_spec.rb +38 -0
- data/spec/lego/either/just_spec.rb +50 -0
- data/spec/lego/either/none_spec.rb +37 -0
- data/spec/lego/model_spec.rb +93 -0
- data/spec/lego/value/base_spec.rb +14 -0
- data/spec/lego/value/boolean_spec.rb +62 -0
- data/spec/lego/value/date_spec.rb +71 -0
- data/spec/lego/value/float_spec.rb +93 -0
- data/spec/lego/value/integer_spec.rb +86 -0
- data/spec/lego/value/set_spec.rb +77 -0
- data/spec/lego/value/string_spec.rb +106 -0
- data/spec/spec_helper.rb +38 -0
- metadata +142 -0
@@ -0,0 +1,20 @@
|
|
1
|
+
module Lego::Value
|
2
|
+
class String < Base
|
3
|
+
|
4
|
+
def parsers
|
5
|
+
[
|
6
|
+
->(v) { v.respond_to?(:to_str) ? Lego.just(v.to_str) : Lego.fail("invalid string: '#{v}'") },
|
7
|
+
->(v) { strip? ? Lego.just(v.strip) : Lego.just(v) },
|
8
|
+
->(v) { (not allow_blank? and v.blank?) ? Lego.none : Lego.just(v) },
|
9
|
+
]
|
10
|
+
end
|
11
|
+
|
12
|
+
def allow_blank?
|
13
|
+
@opts.fetch(:allow_blank, false)
|
14
|
+
end
|
15
|
+
|
16
|
+
def strip?
|
17
|
+
@opts.fetch(:strip, true)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Lego::Either::Fail do
|
4
|
+
|
5
|
+
subject { Lego::Either::Fail.new(2) }
|
6
|
+
|
7
|
+
describe '#initialize' do
|
8
|
+
|
9
|
+
its(:error?) { should == true }
|
10
|
+
its(:none?) { should == false }
|
11
|
+
its(:value?) { should == false }
|
12
|
+
|
13
|
+
its(:error) { should == 2 }
|
14
|
+
|
15
|
+
it 'fails for :value' do
|
16
|
+
->{ subject.value }.should raise_error(NoMethodError)
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'defines shortcut' do
|
20
|
+
Lego.fail(1).should be_error(1)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe '#next' do
|
25
|
+
let(:callable) do
|
26
|
+
->(err) { fail 'Do not call me' }
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'does not call callable' do
|
30
|
+
->{ subject.next(callable) }.should_not raise_error
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'returns self' do
|
34
|
+
subject.next(callable).should eql(subject)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Lego::Either::Just do
|
4
|
+
|
5
|
+
subject { Lego::Either::Just.new(2) }
|
6
|
+
|
7
|
+
describe '#initialize' do
|
8
|
+
its(:value?) { should == true }
|
9
|
+
its(:none?) { should == false }
|
10
|
+
its(:error?) { should == false }
|
11
|
+
|
12
|
+
its(:value) { should == 2 }
|
13
|
+
|
14
|
+
it 'fails for :error' do
|
15
|
+
->{ subject.error }.should raise_error(NoMethodError)
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'defines shortcut' do
|
19
|
+
Lego.just(1).should be_value
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
describe '#next' do
|
24
|
+
it 'binds and calls callable' do
|
25
|
+
callable = ->(val) { fail "Call me - #{val}" }
|
26
|
+
->{ subject.next(callable) }.should raise_error('Call me - 2')
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'returns new Just' do
|
30
|
+
callable = ->(num) { Lego.just(num * 20) }
|
31
|
+
subject.next(callable).should be_just(40)
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'returns None' do
|
35
|
+
callable = ->(num) { Lego.none }
|
36
|
+
subject.next(callable).should be_nothing
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'returns Fail' do
|
40
|
+
callable = ->(num) { Lego.fail('oops') }
|
41
|
+
subject.next(callable).should be_error('oops')
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'fails on not Eitherish return value' do
|
45
|
+
callable = ->(num) { 'a string' }
|
46
|
+
->{ subject.next(callable) }.should raise_error(TypeError, /Not Lego::Either/)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Lego::Either::None do
|
4
|
+
|
5
|
+
subject { Lego::Either::None.new }
|
6
|
+
|
7
|
+
its(:none?) { should == true }
|
8
|
+
its(:value?) { should == false }
|
9
|
+
its(:error?) { should == false }
|
10
|
+
|
11
|
+
it 'fails for :value' do
|
12
|
+
->{ subject.value }.should raise_error(NoMethodError)
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'fails for :error' do
|
16
|
+
->{ subject.error }.should raise_error(NoMethodError)
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'defines shortcut' do
|
20
|
+
Lego.none.should be_none
|
21
|
+
end
|
22
|
+
|
23
|
+
describe '#next' do
|
24
|
+
let(:callable) do
|
25
|
+
->(err) { fail 'Do not call me!' }
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'does not call callable' do
|
29
|
+
->{ subject.next(callable) }.should_not raise_error
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'returns self' do
|
33
|
+
subject.next(callable).should eql(subject)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Lego::Model do
|
4
|
+
|
5
|
+
class Person < Lego::Model
|
6
|
+
attribute :name, :string
|
7
|
+
attribute :age, :integer
|
8
|
+
end
|
9
|
+
|
10
|
+
class Family < Lego::Model
|
11
|
+
attribute :last_name, :string
|
12
|
+
attribute :father, Person
|
13
|
+
end
|
14
|
+
|
15
|
+
context 'Person' do
|
16
|
+
subject do
|
17
|
+
Person.new(name: 'Alice', age: '10')
|
18
|
+
end
|
19
|
+
|
20
|
+
it '::attributes' do
|
21
|
+
Person.attribute_names.should == [:name, :age]
|
22
|
+
end
|
23
|
+
|
24
|
+
its(:name){ should == 'Alice' }
|
25
|
+
its(:age){ should == 10 }
|
26
|
+
|
27
|
+
it '#attributes' do
|
28
|
+
subject.attributes.should == { name: 'Alice', age: 10 }
|
29
|
+
subject.attributes.should be_frozen
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'raises error on unknown attribute' do
|
33
|
+
expect{ Person.new(name: 'Alice', age: '10', grade: 5) }.to raise_error(ArgumentError, "Unknown attributes: {:grade=>5}")
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'fails on validation' do
|
37
|
+
expect{ Person.new(name: 'Alice') }.to raise_error(ArgumentError, ":age => missing value")
|
38
|
+
expect{ Person.new(name: 'Alice', age: Date.today) }.to raise_error(ArgumentError, /invalid integer/)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
context 'equality' do
|
43
|
+
it '#==' do
|
44
|
+
Person.new(name: 'Alice', age: 10).should == Person.new(name: 'Alice', age: '10')
|
45
|
+
Person.new(name: 'Alice', age: 10).should_not == Person.new(name: 'Bob', age: '10')
|
46
|
+
Person.new(name: 'Alice', age: 10).should_not == Person.new(name: 'Alice', age: '12')
|
47
|
+
end
|
48
|
+
|
49
|
+
it '#eql?' do
|
50
|
+
Person.new(name: 'Alice', age: 10).should eql Person.new(name: 'Alice', age: '10')
|
51
|
+
Person.new(name: 'Alice', age: 10).should_not eql Person.new(name: 'Bob', age: '10')
|
52
|
+
Person.new(name: 'Alice', age: 10).should_not eql Person.new(name: 'Alice', age: '12')
|
53
|
+
end
|
54
|
+
|
55
|
+
it '#hash' do
|
56
|
+
Person.new(name: 'Alice', age: 10).hash.should == Person.new(name: 'Alice', age: '10').hash
|
57
|
+
Person.new(name: 'Alice', age: 10).hash.should_not == Person.new(name: 'Bob', age: '10').hash
|
58
|
+
Person.new(name: 'Alice', age: 10).hash.should_not == Person.new(name: 'Alice', age: '12').hash
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
context 'Family' do
|
63
|
+
it 'creates recursively from hash' do
|
64
|
+
family = Family.new(last_name: 'Kowalski', father: { name: 'Bob', age: '55' })
|
65
|
+
family.should be_instance_of(Family)
|
66
|
+
family.last_name.should == 'Kowalski'
|
67
|
+
family.father.should == Person.new(name: 'Bob', age: '55')
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'creates from partial hash' do
|
71
|
+
family = Family.new(last_name: 'Kowalski', father: Person.new(name: 'Bob', age: '55'))
|
72
|
+
family.should be_instance_of(Family)
|
73
|
+
family.last_name.should == 'Kowalski'
|
74
|
+
family.father.should == Person.new(name: 'Bob', age: '55')
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
|
79
|
+
describe '#as_json' do
|
80
|
+
it 'serializes Family' do
|
81
|
+
family = Family.new(last_name: 'Kowalski', father: { name: 'Bob', age: '55' })
|
82
|
+
family.as_json.should == {
|
83
|
+
:last_name => "Kowalski",
|
84
|
+
:father => {
|
85
|
+
:name => "Bob",
|
86
|
+
:age => 55
|
87
|
+
}
|
88
|
+
}
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Lego::Value::Boolean do
|
4
|
+
|
5
|
+
describe '#parse' do
|
6
|
+
context 'nil' do
|
7
|
+
specify { subject.parse(nil).should be_nothing }
|
8
|
+
specify { subject.parse('').should be_nothing }
|
9
|
+
end
|
10
|
+
|
11
|
+
context 'boolean string' do
|
12
|
+
specify { subject.parse('true').should be_just(true) }
|
13
|
+
specify { subject.parse('false').should be_just(false) }
|
14
|
+
end
|
15
|
+
|
16
|
+
context 'boolean value' do
|
17
|
+
specify { subject.parse(true).should be_just(true) }
|
18
|
+
specify { subject.parse(false).should be_just(false) }
|
19
|
+
end
|
20
|
+
|
21
|
+
context 'invalid value' do
|
22
|
+
specify { subject.parse('wtf').should be_error("invalid boolean: 'wtf'") }
|
23
|
+
specify { subject.parse(' ').should be_error("invalid boolean: ' '") }
|
24
|
+
specify { subject.parse(0).should be_error("invalid boolean: '0'") }
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe '#coerce' do
|
29
|
+
context 'nothing' do
|
30
|
+
context 'default' do
|
31
|
+
it 'raises error' do
|
32
|
+
expect{ subject.coerce(nil) }.to raise_error(Lego::CoerceError, 'missing value')
|
33
|
+
expect{ subject.coerce('') }.to raise_error(Lego::CoerceError, 'missing value')
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
context 'default handler -> false' do
|
38
|
+
subject { Lego::Value::Boolean.new(default: ->{ false }) }
|
39
|
+
specify { subject.coerce(nil).should == false }
|
40
|
+
specify { subject.coerce('').should == false }
|
41
|
+
specify { subject.coerce(true).should == true }
|
42
|
+
end
|
43
|
+
|
44
|
+
context 'default handler -> true' do
|
45
|
+
subject { Lego::Value::Boolean.new(default: ->{ true }) }
|
46
|
+
specify { subject.coerce(nil).should == true }
|
47
|
+
specify { subject.coerce('').should == true }
|
48
|
+
specify { subject.coerce(false).should == false }
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
context 'failure' do
|
53
|
+
it 'raises error' do
|
54
|
+
expect{ subject.coerce(123) }.to raise_error(Lego::CoerceError, "invalid boolean: '123'")
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
context 'success' do
|
59
|
+
specify { subject.coerce(true).should == true }
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Lego::Value::Date do
|
4
|
+
|
5
|
+
describe '#parse' do
|
6
|
+
subject { Lego::Value::Date.new }
|
7
|
+
|
8
|
+
context 'nil' do
|
9
|
+
specify { subject.parse(nil).should be_nothing }
|
10
|
+
specify { subject.parse('').should be_nothing }
|
11
|
+
end
|
12
|
+
|
13
|
+
context 'invalid date' do
|
14
|
+
specify { subject.parse(123).should be_error("invalid date: '123'") }
|
15
|
+
specify { subject.parse('2012-02').should be_error("invalid date: '2012-02'") }
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'parses date' do
|
19
|
+
subject.parse(Date.new(2012, 02, 01)).should be_just(Date.new(2012, 02, 01))
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'parses time' do
|
23
|
+
subject.parse(Time.new(2012, 02, 01, 8, 30)).should be_just(Date.new(2012, 02, 01))
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'parses string date' do
|
27
|
+
subject.parse('2012-02-01').should be_just(Date.new(2012, 02, 01))
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'parses string time' do
|
31
|
+
subject.parse('2012-02-01T08:30:00Z').should be_just(Date.new(2012, 02, 01))
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
describe '#coerce' do
|
36
|
+
context 'nothing' do
|
37
|
+
context 'default' do
|
38
|
+
it 'raises error' do
|
39
|
+
expect{ subject.coerce(nil) }.to raise_error(Lego::CoerceError, 'missing value')
|
40
|
+
expect{ subject.coerce('') }.to raise_error(Lego::CoerceError, 'missing value')
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
context 'with :default handler' do
|
45
|
+
subject { Lego::Value::String.new(default: handler) }
|
46
|
+
|
47
|
+
context 'nil handler' do
|
48
|
+
let(:handler) { ->{ nil } }
|
49
|
+
specify { subject.coerce(nil).should be_nil }
|
50
|
+
end
|
51
|
+
|
52
|
+
context 'lambda handler' do
|
53
|
+
let(:handler) { proc{ Date.new(2000, 1, 1) } }
|
54
|
+
specify { subject.coerce('').should == Date.new(2000, 1, 1) }
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
context 'failure' do
|
60
|
+
it 'raises error' do
|
61
|
+
expect{ subject.coerce(123) }.to raise_error(Lego::CoerceError, "invalid date: '123'")
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
context 'success' do
|
66
|
+
it 'returns date' do
|
67
|
+
subject.coerce(Date.new(2012, 02, 01)).should == Date.new(2012, 02, 01)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Lego::Value::Float do
|
4
|
+
|
5
|
+
describe '#parse' do
|
6
|
+
subject { Lego::Value::Float.new(opts) }
|
7
|
+
|
8
|
+
let(:opts){ {} }
|
9
|
+
|
10
|
+
context 'nil' do
|
11
|
+
specify { subject.parse(nil).should be_nothing }
|
12
|
+
specify { subject.parse('').should be_nothing }
|
13
|
+
end
|
14
|
+
|
15
|
+
context 'invalid float' do
|
16
|
+
specify { subject.parse(' ').should be_error("invalid float: ' '") }
|
17
|
+
specify { subject.parse('2012-02').should be_error("invalid float: '2012-02'") }
|
18
|
+
specify { subject.parse('invalid').should be_error("invalid float: 'invalid'") }
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'parses integer as float' do
|
22
|
+
subject.parse(123).should be_just(123.0)
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'parses float' do
|
26
|
+
subject.parse(123.12).should be_just(123.12)
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'parses string integer as float' do
|
30
|
+
subject.parse('-134').should be_just(-134.0)
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'parses string float' do
|
34
|
+
subject.parse('-134.11').should be_just(-134.11)
|
35
|
+
end
|
36
|
+
|
37
|
+
context 'minimum' do
|
38
|
+
let(:opts){ { min: 4 } }
|
39
|
+
specify { subject.parse(4).should be_just(4) }
|
40
|
+
specify { subject.parse(3.99999).should be_error("less than minimum of 4: '3.99999'") }
|
41
|
+
end
|
42
|
+
|
43
|
+
context 'maximum' do
|
44
|
+
let(:opts){ { max: 4 } }
|
45
|
+
specify { subject.parse(4).should be_just(4) }
|
46
|
+
specify { subject.parse(4.01).should be_error("more than maximum of 4: '4.01'") }
|
47
|
+
end
|
48
|
+
|
49
|
+
context 'minimium and maximum' do
|
50
|
+
let(:opts){ { min: 0, max: 4 } }
|
51
|
+
specify { subject.parse(1.1).should be_just(1.1) }
|
52
|
+
specify { subject.parse(-0.1).should be_error("less than minimum of 0: '-0.1'") }
|
53
|
+
specify { subject.parse(4.1).should be_error("more than maximum of 4: '4.1'") }
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
describe '#coerce' do
|
58
|
+
context 'nothing' do
|
59
|
+
context 'default' do
|
60
|
+
it 'raises error' do
|
61
|
+
expect{ subject.coerce(nil) }.to raise_error(Lego::CoerceError, 'missing value')
|
62
|
+
expect{ subject.coerce('') }.to raise_error(Lego::CoerceError, 'missing value')
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
context 'with :default handler' do
|
67
|
+
subject { Lego::Value::Float.new(default: handler) }
|
68
|
+
|
69
|
+
context 'nil handler' do
|
70
|
+
let(:handler) { ->{ nil } }
|
71
|
+
specify { subject.coerce(nil).should be_nil }
|
72
|
+
end
|
73
|
+
|
74
|
+
context 'lambda handler' do
|
75
|
+
let(:handler) { ->{ 42.0 } }
|
76
|
+
specify { subject.coerce('').should == 42.0 }
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
context 'failure' do
|
82
|
+
it 'raises error' do
|
83
|
+
expect{ subject.coerce('foo') }.to raise_error(Lego::CoerceError, "invalid float: 'foo'")
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
context 'success' do
|
88
|
+
it 'returns float' do
|
89
|
+
subject.coerce('42').should == 42.0
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|