id 0.0.12 → 0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +0 -3
- data/Gemfile.lock +25 -10
- data/LICENSE.md +1 -1
- data/README.md +173 -35
- data/id.gemspec +8 -3
- data/lib/id.rb +21 -15
- data/lib/id/active_model.rb +30 -0
- data/lib/id/association.rb +26 -0
- data/lib/id/boolean.rb +8 -0
- data/lib/id/coercion.rb +38 -0
- data/lib/id/eta_expansion.rb +5 -0
- data/lib/id/field.rb +46 -0
- data/lib/id/field/definition.rb +44 -0
- data/lib/id/field/summary.rb +35 -0
- data/lib/id/form.rb +41 -13
- data/lib/id/form_backwards_compatibility.rb +6 -0
- data/lib/id/hashifier.rb +13 -24
- data/lib/id/model.rb +29 -25
- data/lib/id/timestamps.rb +15 -15
- data/lib/id/validations.rb +8 -0
- data/spec/examples/cat_spec.rb +37 -0
- data/spec/lib/id/active_model_spec.rb +40 -0
- data/spec/lib/id/association_spec.rb +73 -0
- data/spec/lib/id/boolean_spec.rb +38 -0
- data/spec/lib/id/coercion_spec.rb +53 -0
- data/spec/lib/id/eta_expansion_spec.rb +13 -0
- data/spec/lib/id/field/definition_spec.rb +37 -0
- data/spec/lib/id/field/summary_spec.rb +26 -0
- data/spec/lib/id/field_spec.rb +62 -0
- data/spec/lib/id/form_spec.rb +84 -0
- data/spec/lib/id/hashifier_spec.rb +19 -0
- data/spec/lib/id/model_spec.rb +30 -180
- data/spec/lib/id/timestamps_spec.rb +22 -20
- data/spec/lib/id/validations_spec.rb +18 -0
- data/spec/spec_helper.rb +11 -5
- data/spec/support/dummy_rails_form_builder.rb +9 -0
- metadata +84 -26
- data/lib/id/form/active_model_form.rb +0 -41
- data/lib/id/form/descriptor.rb +0 -27
- data/lib/id/form/field_form.rb +0 -14
- data/lib/id/form/field_with_form_support.rb +0 -12
- data/lib/id/missing_attribute_error.rb +0 -4
- data/lib/id/model/association.rb +0 -49
- data/lib/id/model/definer.rb +0 -23
- data/lib/id/model/descriptor.rb +0 -23
- data/lib/id/model/field.rb +0 -61
- data/lib/id/model/has_many.rb +0 -11
- data/lib/id/model/has_one.rb +0 -17
- data/lib/id/model/type_casts.rb +0 -96
- data/spec/lib/id/model/association_spec.rb +0 -27
- data/spec/lib/id/model/field_spec.rb +0 -0
- data/spec/lib/id/model/form_spec.rb +0 -56
- data/spec/lib/id/model/type_casts_spec.rb +0 -44
- data/spec/lib/mike_spec.rb +0 -20
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'support/active_model_lint'
|
3
|
+
|
4
|
+
class ActiveModelTest
|
5
|
+
include Id::Model
|
6
|
+
include Id::Form
|
7
|
+
|
8
|
+
field :sheep
|
9
|
+
|
10
|
+
def wrapped_in_plastic?
|
11
|
+
true
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
describe ActiveModelTest do
|
16
|
+
|
17
|
+
subject { ActiveModelTest.new.to_model }
|
18
|
+
|
19
|
+
it_behaves_like "ActiveModel"
|
20
|
+
|
21
|
+
it 'has an I18n scope of id' do
|
22
|
+
expect(ActiveModelTest.new.to_model.class.i18n_scope).to eq :id
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'can return the fields of the model' do
|
26
|
+
model = ActiveModelTest.new(sheep: 42).to_model
|
27
|
+
expect(model.sheep).to eq 42
|
28
|
+
end
|
29
|
+
|
30
|
+
it "doesn't blow up for nil fields" do
|
31
|
+
model = ActiveModelTest.new.to_model
|
32
|
+
expect(model.sheep).to be_nil
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'delegates method calls to the original model' do
|
36
|
+
model = ActiveModelTest.new.to_model
|
37
|
+
expect(model).to be_wrapped_in_plastic
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
class Tail; include Id::Model; field :waggy end
|
4
|
+
class Paw; include Id::Model end
|
5
|
+
|
6
|
+
class Dog
|
7
|
+
include Id::Model
|
8
|
+
has_one :tail
|
9
|
+
has_many :paws
|
10
|
+
|
11
|
+
class Nose; include Id::Model; field :wet end
|
12
|
+
has_one :nose
|
13
|
+
|
14
|
+
class Poo; include Id::Model end
|
15
|
+
has_many :poos
|
16
|
+
end
|
17
|
+
|
18
|
+
module Clothing
|
19
|
+
class Button; include Id::Model end
|
20
|
+
class Coat
|
21
|
+
include Id::Model
|
22
|
+
has_many :buttons
|
23
|
+
end
|
24
|
+
class Lapel
|
25
|
+
include Id::Model
|
26
|
+
has_one :button
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe Id::Association do
|
31
|
+
|
32
|
+
describe '#has_one' do
|
33
|
+
|
34
|
+
it 'infers its type' do
|
35
|
+
dog = Dog.new(tail: { waggy: true })
|
36
|
+
expect(dog.tail).to be_a Tail
|
37
|
+
expect(dog.tail.waggy).to be_true
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'can deal with types nested inside the model class' do
|
41
|
+
dog = Dog.new(nose: { wet: true })
|
42
|
+
expect(dog.nose).to be_a Dog::Nose
|
43
|
+
expect(dog.nose.wet).to be_true
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'can deal with types nested inside a parent module' do
|
47
|
+
lapel = Clothing::Lapel.new(button: {})
|
48
|
+
expect(lapel.button).to be_a Clothing::Button
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
describe '#has_many' do
|
54
|
+
it 'infers its type' do
|
55
|
+
dog = Dog.new(paws: [{}, {}, {}])
|
56
|
+
expect(dog.paws).to have(3).items
|
57
|
+
dog.paws.each { |paw| expect(paw).to be_a Paw }
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'can deal with types nested inside the model class' do
|
61
|
+
dog = Dog.new(poos: [{}, {}, {}])
|
62
|
+
expect(dog.poos).to have(3).items
|
63
|
+
dog.poos.each { |poo| expect(poo).to be_a Dog::Poo }
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'can deal with types nested inside a parent module' do
|
67
|
+
coat = Clothing::Coat.new(buttons: [{}, {}, {}])
|
68
|
+
expect(coat.buttons).to have(3).items
|
69
|
+
coat.buttons.each { |button| expect(button).to be_a Clothing::Button }
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Id::Boolean do
|
4
|
+
it 'parses the word true as true' do
|
5
|
+
b = Id::Boolean.parse('true')
|
6
|
+
expect(b).to be_true
|
7
|
+
end
|
8
|
+
|
9
|
+
it 'parses the word yes as true' do
|
10
|
+
b = Id::Boolean.parse('yes')
|
11
|
+
expect(b).to be_true
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'is case insensite when parsing a string' do
|
15
|
+
b = Id::Boolean.parse('tRuE')
|
16
|
+
expect(b).to be_true
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'parses the character 1 as true' do
|
20
|
+
b = Id::Boolean.parse('1')
|
21
|
+
expect(b).to be_true
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'parses the number 1 as true' do
|
25
|
+
b = Id::Boolean.parse(1)
|
26
|
+
expect(b).to be_true
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'parses symbols as well as strings' do
|
30
|
+
b = Id::Boolean.parse(:true)
|
31
|
+
expect(b).to be_true
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'parses everything else as false' do
|
35
|
+
b = Id::Boolean.parse(:cottage_cheese)
|
36
|
+
expect(b).to be_false
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Id::Coercion do
|
4
|
+
|
5
|
+
it 'can coerce basic types' do
|
6
|
+
coerced = Id::Coercion.coerce("2", Integer)
|
7
|
+
expect(coerced).to eq 2
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'can coerce strings into dates' do
|
11
|
+
coerced = Id::Coercion.coerce("01/01/2001", Date)
|
12
|
+
expect(coerced).to eq Date.new(2001, 1, 1)
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'can coerce strings into times' do
|
16
|
+
coerced = Id::Coercion.coerce("01/01/2001", Time)
|
17
|
+
expect(coerced).to eq Time.new(2001, 1, 1)
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'can coerce strings into booleans' do
|
21
|
+
coerced = Id::Coercion.coerce("yes", Id::Boolean)
|
22
|
+
expect(coerced).to be_true
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'can have new coercions added, even stupid ones' do
|
26
|
+
Id::Coercion.register(Fixnum, String) { |value| (value + 1).to_s }
|
27
|
+
coerced = Id::Coercion.coerce(2, String)
|
28
|
+
expect(coerced).to eq "3"
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'throws an error if there is no registered coercion for the given types' do
|
32
|
+
expect { Id::Coercion.coerce(2, Proc) }.to raise_error Id::CoercionError
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'can coerce data into id models' do
|
36
|
+
c = Class.new { include Id::Model ; field :cats }
|
37
|
+
coerced = Id::Coercion.coerce({cats: 3}, c)
|
38
|
+
expect(coerced.cats).to eq 3
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'can coerce arrays of a type' do
|
42
|
+
c = Class.new { include Id::Model }
|
43
|
+
coerced = Id::Coercion.coerce([{},{},{}], Array[c])
|
44
|
+
expect(coerced).to have(3).items
|
45
|
+
coerced.each { |item| expect(item).to be_a c }
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'can coerce optional types' do
|
49
|
+
c = Class.new { include Id::Model; field :foot, optional: true, type: Integer }
|
50
|
+
expect(c.new.foot).to be_none
|
51
|
+
expect(c.new(foot: '4').foot).to eq Some[4]
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
class Foot
|
4
|
+
include Id::Model
|
5
|
+
end
|
6
|
+
|
7
|
+
describe Id::EtaExpansion do
|
8
|
+
it 'eta expands an id model class into its constructor' do
|
9
|
+
feet = [{},{},{}].map(&Foot)
|
10
|
+
expect(feet).to have(3).items
|
11
|
+
feet.each { |foot| expect(foot).to be_a Foot }
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Id::Field
|
4
|
+
describe Definition do
|
5
|
+
|
6
|
+
it 'has a name' do
|
7
|
+
definition = Definition.new(:cats, {})
|
8
|
+
expect(definition.name).to eq :cats
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'can be optional' do
|
12
|
+
definition = Definition.new(:cats, optional: true)
|
13
|
+
expect(definition).to be_optional
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'has a type' do
|
17
|
+
definition = Definition.new(:cats, type: String)
|
18
|
+
expect(definition.type).to eq String
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'has a string key' do
|
22
|
+
definition = Definition.new(:cats, key: 'kitten')
|
23
|
+
expect(definition.key).to eq 'kitten'
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'has a default' do
|
27
|
+
definition = Definition.new(:cats, default: ->{ Time.now })
|
28
|
+
expect(definition.default).to be_a Proc
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'has a default! method which executes the default if it is a lambda' do
|
32
|
+
definition = Definition.new(:cats, default: ->{ Time.now })
|
33
|
+
expect(definition.default!).to be_a Time
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Id::Field
|
4
|
+
describe Summary do
|
5
|
+
it 'constructs a summary of the definition' do
|
6
|
+
definition = Id::Field::Definition.new(:cats,
|
7
|
+
optional: true,
|
8
|
+
key: 'kittens',
|
9
|
+
type: String,
|
10
|
+
default: -> { "KITTEH!"})
|
11
|
+
summary = definition.to_s
|
12
|
+
expect(summary).to eq "Name: cats\n" +
|
13
|
+
"Type: String\n" +
|
14
|
+
"Key in hash: kittens\n" +
|
15
|
+
"Optional: true\n" +
|
16
|
+
"Default: Lambda"
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'omits info for options that are not specified' do
|
20
|
+
definition = Id::Field::Definition.new(:cats, type: String)
|
21
|
+
summary = definition.to_s
|
22
|
+
expect(summary).to eq "Name: cats\n" +
|
23
|
+
"Type: String"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Id::Field do
|
4
|
+
|
5
|
+
it 'allows key aliases to be defined' do
|
6
|
+
c = Class.new { include Id::Model ; field :cats, key: 'kittehs' }.new(kittehs: 3)
|
7
|
+
expect(c.cats).to eq 3
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'blows up with an informative error if you try to request a nil value' do
|
11
|
+
class TestClass; include Id::Model; field :cats end
|
12
|
+
c = TestClass.new
|
13
|
+
expect { c.cats }.to raise_error Id::MissingAttributeError, /TestClass had a nil value for 'cats'./
|
14
|
+
end
|
15
|
+
|
16
|
+
describe 'type coercion' do
|
17
|
+
|
18
|
+
it 'can coerce basic types' do
|
19
|
+
c = Class.new { include Id::Model ; field :cats, type: Integer }.new(cats: "3")
|
20
|
+
expect(c.cats).to eq 3
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe 'defaults' do
|
25
|
+
|
26
|
+
it 'can be initialised with a default value' do
|
27
|
+
c = Class.new { include Id::Model ; field :cats, default: 7 }.new
|
28
|
+
expect(c.cats).to eq 7
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'can take a lambda as a default value' do
|
32
|
+
c = Class.new { include Id::Model ; field :cats, default: -> { 7 } }.new
|
33
|
+
expect(c.cats).to eq 7
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'runs lambda defaults at the point of first access of the field' do
|
37
|
+
c = Class.new { include Id::Model ; field :time, default: -> { Time.now } }.new
|
38
|
+
d = Class.new { include Id::Model ; field :time, default: -> { Time.now } }.new
|
39
|
+
t1 = c.time; sleep 0.0001; t2 = d.time
|
40
|
+
expect(t2).to be > t1
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'memoizes lambda generated values so they are created only once' do
|
44
|
+
c = Class.new { include Id::Model ; field :time, default: -> { Time.now } }.new
|
45
|
+
t1 = c.time; sleep 0.0001; t2 = c.time
|
46
|
+
expect(t2).to eq t1
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'can test for the presence of values' do
|
52
|
+
c = Class.new { include Id::Model ; field :foo }.new
|
53
|
+
expect(c.foo?).to be_false
|
54
|
+
d = c.set(foo: 4)
|
55
|
+
expect(d.foo?).to be_true
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'makes the fields of the model enumerable' do
|
59
|
+
c = Class.new { include Id::Model ; field :foo }.new
|
60
|
+
expect(c.fields.keys.map(&:to_s).map(&:upcase)).to eq ["FOO"]
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Id::Form do
|
4
|
+
|
5
|
+
class FormTest
|
6
|
+
include Id::Model
|
7
|
+
include Id::Form
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'defines self.model_name' do
|
11
|
+
expect(FormTest.new.to_model.class.model_name).to eq 'FormTest'
|
12
|
+
end
|
13
|
+
|
14
|
+
module Enclosing
|
15
|
+
class FormTest2
|
16
|
+
include Id::Model
|
17
|
+
include Id::Form
|
18
|
+
|
19
|
+
def self.model_name
|
20
|
+
ActiveModel::Name.new(self, nil, "Harry")
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
it "defers to the class' implementation of model_name if it has one" do
|
26
|
+
expect(Enclosing::FormTest2.new.to_model.class.model_name).to eq 'Harry'
|
27
|
+
expect(Enclosing::FormTest2.new.to_model.class.model_name.plural).to eq 'harries'
|
28
|
+
end
|
29
|
+
|
30
|
+
class FormTest3
|
31
|
+
include Id::Model
|
32
|
+
include Id::Form
|
33
|
+
|
34
|
+
def to_partial_path
|
35
|
+
'Jemima'
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
it "defers to the class' implementation of to_partial_path if it has one" do
|
40
|
+
expect(FormTest3.new.to_model.to_partial_path).to eq 'Jemima'
|
41
|
+
end
|
42
|
+
|
43
|
+
class Octopus
|
44
|
+
include Id::Model
|
45
|
+
include Id::Form
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'defines a sensible default implementation of to_partial_path' do
|
49
|
+
expect(Octopus.new.to_model.to_partial_path).to eq 'octopi/octopus'
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'still allows the use of as_form, but this is deprecated' do
|
53
|
+
octopus = Octopus.new
|
54
|
+
expect(octopus.as_form).to eq octopus.to_model
|
55
|
+
end
|
56
|
+
|
57
|
+
class Cheetah
|
58
|
+
include Id::Model
|
59
|
+
include Id::Form
|
60
|
+
field :foo
|
61
|
+
form do
|
62
|
+
validates_presence_of :foo
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'still allows the use of form, but this is deprecated' do
|
67
|
+
expect(Cheetah.new).not_to be_valid
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
|
72
|
+
describe Id::FormBuilder do
|
73
|
+
it 'calls to_model on id models used as form objects' do
|
74
|
+
cat = Class.new { include Id::Model ; field :foo }.new
|
75
|
+
cat.expects(:to_model).returns(cat)
|
76
|
+
builder = Id::FormBuilder.new('cat', cat, 'cat.html.erb', {})
|
77
|
+
end
|
78
|
+
|
79
|
+
it 'does not call to_model on other objects' do
|
80
|
+
dog = Class.new.new
|
81
|
+
dog.expects(:to_model).never
|
82
|
+
builder = Id::FormBuilder.new('dog', dog, 'dog.html.erb', {})
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Id::Hashifier do
|
4
|
+
|
5
|
+
it 'enhashes nested hashes of id models' do
|
6
|
+
t = Class.new { include Id::Model ; field :waggy }
|
7
|
+
c = Class.new { include Id::Model ; has_one :tail, type: t }
|
8
|
+
cat = c.new(tail: {waggy: false})
|
9
|
+
expect(Id::Hashifier.enhash cat).to eq("tail" => { "waggy" => false })
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'enhashes nested arrays of id models' do
|
13
|
+
p = Class.new { include Id::Model }
|
14
|
+
c = Class.new { include Id::Model ; has_many :paws, type: p }
|
15
|
+
cat = c.new(paws: [{},{},{}])
|
16
|
+
expect(Id::Hashifier.enhash cat).to eq("paws" => [{},{},{}])
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|