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