lego 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -12,7 +12,7 @@ Gem::Specification.new do |gem|
12
12
  gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
13
13
  gem.name = "lego"
14
14
  gem.require_paths = ["lib"]
15
- gem.version = '0.0.3'
15
+ gem.version = '0.0.4'
16
16
 
17
17
  gem.add_dependency 'activesupport'
18
18
 
@@ -8,15 +8,15 @@ module Lego
8
8
  require_relative 'lego/model'
9
9
 
10
10
  def self.value_parser(item, *args)
11
- if item.is_a?(Symbol)
12
- Lego::Value.const_get(item.to_s.camelize, false).new(*args)
11
+ if (Lego::Value.const_defined?(item.to_s, false) rescue false)
12
+ Lego::Value.const_get(item.to_s, false).new(*args)
13
13
  elsif item.respond_to?(:coerce)
14
14
  item
15
15
  else
16
- fail NameError
16
+ raise NameError
17
17
  end
18
18
  rescue NameError
19
- fail NameError, "Unknown Lego::Value parser: #{item.to_s.camelize}"
19
+ raise NameError, "Unknown Lego::Value parser: #{item.to_s}"
20
20
  end
21
21
 
22
22
  end
@@ -1,3 +1,5 @@
1
+ require 'active_support/core_ext/hash/deep_merge'
2
+
1
3
  module Lego
2
4
  class Model
3
5
  class << self
@@ -16,9 +18,12 @@ module Lego
16
18
  end
17
19
 
18
20
  def initialize(attrs={})
21
+ fail ArgumentError, "attrs must be hash: '#{attrs.inspect}'" unless attrs.respond_to?(:key?)
22
+ attrs = attrs.dup
19
23
  @attributes = {}.tap do |h|
20
24
  self.class.parsers.each do |name, parser|
21
- value = attrs.delete(name)
25
+ name = name.to_sym
26
+ value = attrs.key?(name) ? attrs.delete(name) : attrs.delete(name.to_s)
22
27
  begin
23
28
  h[name] = parser.coerce(value)
24
29
  rescue Lego::CoerceError => e
@@ -31,6 +36,10 @@ module Lego
31
36
 
32
37
  attr_reader :attributes
33
38
 
39
+ def merge(other)
40
+ self.class.new(as_json.deep_merge(other))
41
+ end
42
+
34
43
  def method_missing(name, *args, &block)
35
44
  attributes.fetch(name.to_sym) { super }
36
45
  end
@@ -58,7 +67,8 @@ module Lego
58
67
 
59
68
  # Serialize
60
69
 
61
- def as_json
70
+ def as_json(opts={})
71
+ raise NotImplementedError, 'as_json with arguments' unless opts.empty?
62
72
  {}.tap do |h|
63
73
  attributes.each do |attr, val|
64
74
  h[attr] = val.as_json
@@ -8,6 +8,7 @@ end
8
8
 
9
9
  require_relative 'value/base'
10
10
  require_relative 'value/set'
11
+ require_relative 'value/array'
11
12
  require_relative 'value/string'
12
13
  require_relative 'value/date'
13
14
  require_relative 'value/integer'
@@ -0,0 +1,50 @@
1
+ module Lego::Value
2
+ class Array < Base
3
+
4
+ def initialize(type, opts={})
5
+ @_item_parser = Lego.value_parser(type)
6
+ super(opts)
7
+ end
8
+
9
+ def parsers
10
+ [
11
+ ->(v) { v.respond_to?(:to_ary) ? Lego.just(v.to_ary) : Lego.fail("invalid array: '#{v}'") },
12
+ ->(v) { check_length? ? enforce_length(v) : Lego.just(v) },
13
+ ->(v) { parse_items(v) },
14
+ ->(v) { (not allow_empty? and v.empty?) ? Lego.none : Lego.just(v) }
15
+ ]
16
+ end
17
+
18
+ private
19
+
20
+ def parse_items(items)
21
+ items = items.map do |item|
22
+ new_item = @_item_parser.parse(item)
23
+ if new_item.value?
24
+ new_item.value
25
+ else
26
+ return new_item
27
+ end
28
+ end
29
+ Lego.just(items)
30
+ end
31
+
32
+ def check_length?
33
+ @opts.key?(:length)
34
+ end
35
+
36
+ def allow_empty?
37
+ @opts.fetch(:allow_empty, false)
38
+ end
39
+
40
+ def enforce_length(items)
41
+ length = @opts.fetch(:length).to_i
42
+ if items.length == length
43
+ Lego.just(items)
44
+ else
45
+ Lego.fail("length not #{length}: '#{items}'")
46
+ end
47
+ end
48
+
49
+ end
50
+ end
@@ -6,6 +6,7 @@ module Lego::Value
6
6
  ->(v) { v.respond_to?(:to_str) ? Lego.just(v.to_str) : Lego.fail("invalid string: '#{v}'") },
7
7
  ->(v) { strip? ? Lego.just(v.strip) : Lego.just(v) },
8
8
  ->(v) { (not allow_blank? and v.blank?) ? Lego.none : Lego.just(v) },
9
+ ->(v) { check_regexp? ? assert_regexp(v) : Lego.just(v) }
9
10
  ]
10
11
  end
11
12
 
@@ -16,5 +17,21 @@ module Lego::Value
16
17
  def strip?
17
18
  @opts.fetch(:strip, true)
18
19
  end
20
+
21
+ private
22
+
23
+ def check_regexp?
24
+ @opts.key?(:matches)
25
+ end
26
+
27
+ def assert_regexp(v)
28
+ regex = @opts.fetch(:matches)
29
+ if regex =~ v
30
+ Lego.just(v)
31
+ else
32
+ Lego.fail("does not match (#{regex.inspect}): '#{v}'")
33
+ end
34
+ end
35
+
19
36
  end
20
37
  end
@@ -3,12 +3,12 @@ require 'spec_helper'
3
3
  describe Lego::Model do
4
4
 
5
5
  class Person < Lego::Model
6
- attribute :name, :string
7
- attribute :age, :integer
6
+ attribute :name, String
7
+ attribute :age, Integer
8
8
  end
9
9
 
10
10
  class Family < Lego::Model
11
- attribute :last_name, :string
11
+ attribute :last_name, String
12
12
  attribute :father, Person
13
13
  end
14
14
 
@@ -37,6 +37,16 @@ describe Lego::Model do
37
37
  expect{ Person.new(name: 'Alice') }.to raise_error(ArgumentError, ":age => missing value")
38
38
  expect{ Person.new(name: 'Alice', age: Date.today) }.to raise_error(ArgumentError, /invalid integer/)
39
39
  end
40
+
41
+ it 'fails on non-hash initialize' do
42
+ expect{ Person.new(nil) }.to raise_error(ArgumentError, "attrs must be hash: 'nil'")
43
+ end
44
+
45
+ it 'dupes attributes' do
46
+ h = { name: 'Alice', age: 10 }
47
+ Person.new(h)
48
+ h.should == { name: 'Alice', age: 10 }
49
+ end
40
50
  end
41
51
 
42
52
  context 'equality' do
@@ -73,6 +83,13 @@ describe Lego::Model do
73
83
  family.last_name.should == 'Kowalski'
74
84
  family.father.should == Person.new(name: 'Bob', age: '55')
75
85
  end
86
+
87
+ it 'initializes from string keys' do
88
+ family = Family.new('last_name' => 'Kowalski', 'father' => Person.new('name' => 'Bob', 'age' => '55'))
89
+ family.should be_instance_of(Family)
90
+ family.last_name.should == 'Kowalski'
91
+ family.father.should == Person.new(name: 'Bob', age: '55')
92
+ end
76
93
  end
77
94
 
78
95
 
@@ -87,7 +104,23 @@ describe Lego::Model do
87
104
  }
88
105
  }
89
106
  end
107
+ end
90
108
 
109
+ describe '#merge' do
110
+ let(:one) { Family.new(last_name: 'Kowalski', father: { name: 'Bob', age: '55' }) }
111
+
112
+ it 'returns new object' do
113
+ two = one.merge({})
114
+ two.should == one
115
+ two.should_not equal(one)
116
+ end
117
+
118
+ it 'merges changes' do
119
+ two = one.merge(last_name: 'Tesla', father: { name: 'Nikola' })
120
+
121
+ one.should == Family.new(last_name: 'Kowalski', father: { name: 'Bob', age: '55' })
122
+ two.should == Family.new(last_name: 'Tesla', father: { name: 'Nikola', age: '55' })
123
+ end
91
124
  end
92
125
 
93
126
  end
@@ -0,0 +1,76 @@
1
+ require 'spec_helper'
2
+
3
+ describe Lego::Value::Array do
4
+
5
+ subject { Lego::Value::Array.new(String) }
6
+
7
+ describe '#parse' do
8
+ context 'nil' do
9
+ specify { subject.parse(nil).should be_nothing }
10
+ end
11
+
12
+ context 'invalid array or item' do
13
+ specify { subject.parse('').should be_error("invalid array: ''") }
14
+ specify { subject.parse('2012-02').should be_error("invalid array: '2012-02'") }
15
+ specify { subject.parse(['one', 123, 'two']).should be_error("invalid string: '123'") }
16
+ end
17
+
18
+ it 'parses array' do
19
+ subject.parse(['one']).should be_just(['one'])
20
+ end
21
+
22
+ context 'allow empty' do
23
+ subject { Lego::Value::Array.new(String, allow_empty: true) }
24
+ specify { subject.parse([]).should be_just([]) }
25
+ end
26
+
27
+ context 'disallow empty' do
28
+ subject { Lego::Value::Array.new(String, allow_empty: false) }
29
+ specify { subject.parse([]).should be_nothing }
30
+ end
31
+
32
+ context 'check length' do
33
+ subject { Lego::Value::Array.new(Integer, length: 2) }
34
+ specify { subject.parse([]).should be_error("length not 2: '[]'") }
35
+ specify { subject.parse([1]).should be_error("length not 2: '[1]'") }
36
+ specify { subject.parse([1, 2, 3]).should be_error("length not 2: '[1, 2, 3]'") }
37
+ specify { subject.parse([42, 24]).should be_just([42, 24]) }
38
+ end
39
+ end
40
+
41
+ describe '#coerce' do
42
+ context 'missing' do
43
+ it 'raises error' do
44
+ expect{ subject.coerce(nil) }.to raise_error(Lego::CoerceError, 'missing value')
45
+ expect{ subject.coerce([]) }.to raise_error(Lego::CoerceError, 'missing value')
46
+ end
47
+
48
+ context 'with :default handler' do
49
+ subject { Lego::Value::Array.new(String, default: handler) }
50
+
51
+ context 'nil handler' do
52
+ let(:handler) { ->{ nil } }
53
+ specify { subject.coerce(nil).should be_nil }
54
+ end
55
+
56
+ context 'lambda handler' do
57
+ let(:handler) { proc{ ['default'] } }
58
+ specify { subject.coerce(nil).should == ['default'] }
59
+ end
60
+ end
61
+ end
62
+
63
+ context 'failure' do
64
+ it 'raises error' do
65
+ expect{ subject.coerce([123]) }.to raise_error(Lego::CoerceError, "invalid string: '123'")
66
+ end
67
+ end
68
+
69
+ context 'success' do
70
+ it 'returns array' do
71
+ subject.coerce(['foo']).should == ['foo']
72
+ end
73
+ end
74
+ end
75
+
76
+ end
@@ -6,7 +6,7 @@ describe Lego::Value::Set do
6
6
  Array(elem).to_set
7
7
  end
8
8
 
9
- subject { Lego::Value::Set.new(:string) }
9
+ subject { Lego::Value::Set.new(String) }
10
10
 
11
11
  describe '#parse' do
12
12
  context 'nil' do
@@ -28,12 +28,12 @@ describe Lego::Value::Set do
28
28
  end
29
29
 
30
30
  context 'allow empty' do
31
- subject { Lego::Value::Set.new(:string, allow_empty: true) }
31
+ subject { Lego::Value::Set.new(String, allow_empty: true) }
32
32
  specify { subject.parse([]).should be_just(set([])) }
33
33
  end
34
34
 
35
35
  context 'disallow empty' do
36
- subject { Lego::Value::Set.new(:string, allow_empty: false) }
36
+ subject { Lego::Value::Set.new(String, allow_empty: false) }
37
37
  specify { subject.parse([]).should be_nothing }
38
38
  end
39
39
  end
@@ -47,7 +47,7 @@ describe Lego::Value::Set do
47
47
  end
48
48
 
49
49
  context 'with :default handler' do
50
- subject { Lego::Value::String.new(default: handler) }
50
+ subject { Lego::Value::Set.new(String, default: handler) }
51
51
 
52
52
  context 'nil handler' do
53
53
  let(:handler) { ->{ nil } }
@@ -56,7 +56,7 @@ describe Lego::Value::Set do
56
56
 
57
57
  context 'lambda handler' do
58
58
  let(:handler) { proc{ Set.new } }
59
- specify { subject.coerce('').should == [].to_set }
59
+ specify { subject.coerce(nil).should == [].to_set }
60
60
  end
61
61
  end
62
62
  end
@@ -57,6 +57,16 @@ describe Lego::Value::String do
57
57
  end
58
58
  end
59
59
 
60
+ context 'matches' do
61
+ subject { Lego::Value::String.new(opts.merge(matches: /^[A-Z]{3}$/)) }
62
+
63
+ specify { subject.parse(nil).should be_nothing }
64
+ specify { subject.parse(123).should be_error("invalid string: '123'") }
65
+
66
+ specify { subject.parse('abc').should be_error("does not match (/^[A-Z]{3}$/): 'abc'") }
67
+ specify { subject.parse('TLC').should be_just('TLC') }
68
+ end
69
+
60
70
  context 'defaults' do
61
71
  subject { Lego::Value::String.new }
62
72
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lego
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.0.4
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-01-17 00:00:00.000000000 Z
12
+ date: 2013-01-18 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activesupport
@@ -78,6 +78,7 @@ files:
78
78
  - lib/lego/either.rb
79
79
  - lib/lego/model.rb
80
80
  - lib/lego/value.rb
81
+ - lib/lego/value/array.rb
81
82
  - lib/lego/value/base.rb
82
83
  - lib/lego/value/boolean.rb
83
84
  - lib/lego/value/date.rb
@@ -89,6 +90,7 @@ files:
89
90
  - spec/lego/either/just_spec.rb
90
91
  - spec/lego/either/none_spec.rb
91
92
  - spec/lego/model_spec.rb
93
+ - spec/lego/value/array_spec.rb
92
94
  - spec/lego/value/base_spec.rb
93
95
  - spec/lego/value/boolean_spec.rb
94
96
  - spec/lego/value/date_spec.rb
@@ -111,7 +113,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
111
113
  version: '0'
112
114
  segments:
113
115
  - 0
114
- hash: 179225649257381862
116
+ hash: 3608539237903599197
115
117
  required_rubygems_version: !ruby/object:Gem::Requirement
116
118
  none: false
117
119
  requirements:
@@ -120,7 +122,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
120
122
  version: '0'
121
123
  segments:
122
124
  - 0
123
- hash: 179225649257381862
125
+ hash: 3608539237903599197
124
126
  requirements: []
125
127
  rubyforge_project:
126
128
  rubygems_version: 1.8.23
@@ -132,6 +134,7 @@ test_files:
132
134
  - spec/lego/either/just_spec.rb
133
135
  - spec/lego/either/none_spec.rb
134
136
  - spec/lego/model_spec.rb
137
+ - spec/lego/value/array_spec.rb
135
138
  - spec/lego/value/base_spec.rb
136
139
  - spec/lego/value/boolean_spec.rb
137
140
  - spec/lego/value/date_spec.rb