lego 0.0.3
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.
- 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
|