id 0.0.12 → 0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +0 -3
  3. data/Gemfile.lock +25 -10
  4. data/LICENSE.md +1 -1
  5. data/README.md +173 -35
  6. data/id.gemspec +8 -3
  7. data/lib/id.rb +21 -15
  8. data/lib/id/active_model.rb +30 -0
  9. data/lib/id/association.rb +26 -0
  10. data/lib/id/boolean.rb +8 -0
  11. data/lib/id/coercion.rb +38 -0
  12. data/lib/id/eta_expansion.rb +5 -0
  13. data/lib/id/field.rb +46 -0
  14. data/lib/id/field/definition.rb +44 -0
  15. data/lib/id/field/summary.rb +35 -0
  16. data/lib/id/form.rb +41 -13
  17. data/lib/id/form_backwards_compatibility.rb +6 -0
  18. data/lib/id/hashifier.rb +13 -24
  19. data/lib/id/model.rb +29 -25
  20. data/lib/id/timestamps.rb +15 -15
  21. data/lib/id/validations.rb +8 -0
  22. data/spec/examples/cat_spec.rb +37 -0
  23. data/spec/lib/id/active_model_spec.rb +40 -0
  24. data/spec/lib/id/association_spec.rb +73 -0
  25. data/spec/lib/id/boolean_spec.rb +38 -0
  26. data/spec/lib/id/coercion_spec.rb +53 -0
  27. data/spec/lib/id/eta_expansion_spec.rb +13 -0
  28. data/spec/lib/id/field/definition_spec.rb +37 -0
  29. data/spec/lib/id/field/summary_spec.rb +26 -0
  30. data/spec/lib/id/field_spec.rb +62 -0
  31. data/spec/lib/id/form_spec.rb +84 -0
  32. data/spec/lib/id/hashifier_spec.rb +19 -0
  33. data/spec/lib/id/model_spec.rb +30 -180
  34. data/spec/lib/id/timestamps_spec.rb +22 -20
  35. data/spec/lib/id/validations_spec.rb +18 -0
  36. data/spec/spec_helper.rb +11 -5
  37. data/spec/support/dummy_rails_form_builder.rb +9 -0
  38. metadata +84 -26
  39. data/lib/id/form/active_model_form.rb +0 -41
  40. data/lib/id/form/descriptor.rb +0 -27
  41. data/lib/id/form/field_form.rb +0 -14
  42. data/lib/id/form/field_with_form_support.rb +0 -12
  43. data/lib/id/missing_attribute_error.rb +0 -4
  44. data/lib/id/model/association.rb +0 -49
  45. data/lib/id/model/definer.rb +0 -23
  46. data/lib/id/model/descriptor.rb +0 -23
  47. data/lib/id/model/field.rb +0 -61
  48. data/lib/id/model/has_many.rb +0 -11
  49. data/lib/id/model/has_one.rb +0 -17
  50. data/lib/id/model/type_casts.rb +0 -96
  51. data/spec/lib/id/model/association_spec.rb +0 -27
  52. data/spec/lib/id/model/field_spec.rb +0 -0
  53. data/spec/lib/id/model/form_spec.rb +0 -56
  54. data/spec/lib/id/model/type_casts_spec.rb +0 -44
  55. 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