id 0.0.12 → 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.
- 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
|