granite-form 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (134) hide show
  1. checksums.yaml +7 -0
  2. data/.codeclimate.yml +13 -0
  3. data/.github/workflows/ci.yml +35 -0
  4. data/.github/workflows/main.yml +29 -0
  5. data/.gitignore +21 -0
  6. data/.rspec +2 -0
  7. data/.rubocop.yml +64 -0
  8. data/.rubocop_todo.yml +48 -0
  9. data/Appraisals +8 -0
  10. data/CHANGELOG.md +73 -0
  11. data/Gemfile +8 -0
  12. data/Guardfile +77 -0
  13. data/LICENSE +22 -0
  14. data/README.md +429 -0
  15. data/Rakefile +6 -0
  16. data/gemfiles/rails.4.2.gemfile +15 -0
  17. data/gemfiles/rails.5.0.gemfile +15 -0
  18. data/gemfiles/rails.5.1.gemfile +15 -0
  19. data/gemfiles/rails.5.2.gemfile +15 -0
  20. data/gemfiles/rails.6.0.gemfile +14 -0
  21. data/gemfiles/rails.6.1.gemfile +14 -0
  22. data/gemfiles/rails.7.0.gemfile +14 -0
  23. data/granite-form.gemspec +31 -0
  24. data/lib/granite/form/active_record/associations.rb +57 -0
  25. data/lib/granite/form/active_record/nested_attributes.rb +20 -0
  26. data/lib/granite/form/base.rb +15 -0
  27. data/lib/granite/form/config.rb +42 -0
  28. data/lib/granite/form/errors.rb +111 -0
  29. data/lib/granite/form/extensions.rb +36 -0
  30. data/lib/granite/form/model/associations/base.rb +97 -0
  31. data/lib/granite/form/model/associations/collection/embedded.rb +14 -0
  32. data/lib/granite/form/model/associations/collection/proxy.rb +35 -0
  33. data/lib/granite/form/model/associations/embeds_any.rb +19 -0
  34. data/lib/granite/form/model/associations/embeds_many.rb +152 -0
  35. data/lib/granite/form/model/associations/embeds_one.rb +112 -0
  36. data/lib/granite/form/model/associations/nested_attributes.rb +215 -0
  37. data/lib/granite/form/model/associations/persistence_adapters/active_record/referenced_proxy.rb +33 -0
  38. data/lib/granite/form/model/associations/persistence_adapters/active_record.rb +68 -0
  39. data/lib/granite/form/model/associations/persistence_adapters/base.rb +55 -0
  40. data/lib/granite/form/model/associations/references_any.rb +43 -0
  41. data/lib/granite/form/model/associations/references_many.rb +113 -0
  42. data/lib/granite/form/model/associations/references_one.rb +88 -0
  43. data/lib/granite/form/model/associations/reflections/base.rb +92 -0
  44. data/lib/granite/form/model/associations/reflections/embeds_any.rb +52 -0
  45. data/lib/granite/form/model/associations/reflections/embeds_many.rb +17 -0
  46. data/lib/granite/form/model/associations/reflections/embeds_one.rb +19 -0
  47. data/lib/granite/form/model/associations/reflections/references_any.rb +65 -0
  48. data/lib/granite/form/model/associations/reflections/references_many.rb +30 -0
  49. data/lib/granite/form/model/associations/reflections/references_one.rb +32 -0
  50. data/lib/granite/form/model/associations/reflections/singular.rb +37 -0
  51. data/lib/granite/form/model/associations/validations.rb +41 -0
  52. data/lib/granite/form/model/associations.rb +120 -0
  53. data/lib/granite/form/model/attributes/attribute.rb +75 -0
  54. data/lib/granite/form/model/attributes/base.rb +134 -0
  55. data/lib/granite/form/model/attributes/collection.rb +19 -0
  56. data/lib/granite/form/model/attributes/dictionary.rb +28 -0
  57. data/lib/granite/form/model/attributes/localized.rb +44 -0
  58. data/lib/granite/form/model/attributes/reference_many.rb +21 -0
  59. data/lib/granite/form/model/attributes/reference_one.rb +52 -0
  60. data/lib/granite/form/model/attributes/reflections/attribute.rb +61 -0
  61. data/lib/granite/form/model/attributes/reflections/base.rb +62 -0
  62. data/lib/granite/form/model/attributes/reflections/collection.rb +12 -0
  63. data/lib/granite/form/model/attributes/reflections/dictionary.rb +15 -0
  64. data/lib/granite/form/model/attributes/reflections/localized.rb +45 -0
  65. data/lib/granite/form/model/attributes/reflections/reference_many.rb +12 -0
  66. data/lib/granite/form/model/attributes/reflections/reference_one.rb +49 -0
  67. data/lib/granite/form/model/attributes/reflections/represents.rb +56 -0
  68. data/lib/granite/form/model/attributes/represents.rb +67 -0
  69. data/lib/granite/form/model/attributes.rb +204 -0
  70. data/lib/granite/form/model/callbacks.rb +72 -0
  71. data/lib/granite/form/model/conventions.rb +40 -0
  72. data/lib/granite/form/model/dirty.rb +84 -0
  73. data/lib/granite/form/model/lifecycle.rb +309 -0
  74. data/lib/granite/form/model/localization.rb +26 -0
  75. data/lib/granite/form/model/persistence.rb +59 -0
  76. data/lib/granite/form/model/primary.rb +59 -0
  77. data/lib/granite/form/model/representation.rb +101 -0
  78. data/lib/granite/form/model/scopes.rb +118 -0
  79. data/lib/granite/form/model/validations/associated.rb +22 -0
  80. data/lib/granite/form/model/validations/nested.rb +56 -0
  81. data/lib/granite/form/model/validations.rb +29 -0
  82. data/lib/granite/form/model.rb +33 -0
  83. data/lib/granite/form/railtie.rb +9 -0
  84. data/lib/granite/form/undefined_class.rb +11 -0
  85. data/lib/granite/form/version.rb +5 -0
  86. data/lib/granite/form.rb +163 -0
  87. data/spec/lib/granite/form/active_record/associations_spec.rb +211 -0
  88. data/spec/lib/granite/form/active_record/nested_attributes_spec.rb +15 -0
  89. data/spec/lib/granite/form/config_spec.rb +66 -0
  90. data/spec/lib/granite/form/model/associations/embeds_many_spec.rb +706 -0
  91. data/spec/lib/granite/form/model/associations/embeds_one_spec.rb +533 -0
  92. data/spec/lib/granite/form/model/associations/nested_attributes_spec.rb +119 -0
  93. data/spec/lib/granite/form/model/associations/persistence_adapters/active_record_spec.rb +58 -0
  94. data/spec/lib/granite/form/model/associations/references_many_spec.rb +572 -0
  95. data/spec/lib/granite/form/model/associations/references_one_spec.rb +445 -0
  96. data/spec/lib/granite/form/model/associations/reflections/embeds_any_spec.rb +42 -0
  97. data/spec/lib/granite/form/model/associations/reflections/embeds_many_spec.rb +145 -0
  98. data/spec/lib/granite/form/model/associations/reflections/embeds_one_spec.rb +117 -0
  99. data/spec/lib/granite/form/model/associations/reflections/references_many_spec.rb +303 -0
  100. data/spec/lib/granite/form/model/associations/reflections/references_one_spec.rb +287 -0
  101. data/spec/lib/granite/form/model/associations/validations_spec.rb +137 -0
  102. data/spec/lib/granite/form/model/associations_spec.rb +198 -0
  103. data/spec/lib/granite/form/model/attributes/attribute_spec.rb +186 -0
  104. data/spec/lib/granite/form/model/attributes/base_spec.rb +97 -0
  105. data/spec/lib/granite/form/model/attributes/collection_spec.rb +72 -0
  106. data/spec/lib/granite/form/model/attributes/dictionary_spec.rb +100 -0
  107. data/spec/lib/granite/form/model/attributes/localized_spec.rb +103 -0
  108. data/spec/lib/granite/form/model/attributes/reflections/attribute_spec.rb +72 -0
  109. data/spec/lib/granite/form/model/attributes/reflections/base_spec.rb +56 -0
  110. data/spec/lib/granite/form/model/attributes/reflections/collection_spec.rb +37 -0
  111. data/spec/lib/granite/form/model/attributes/reflections/dictionary_spec.rb +43 -0
  112. data/spec/lib/granite/form/model/attributes/reflections/localized_spec.rb +37 -0
  113. data/spec/lib/granite/form/model/attributes/reflections/represents_spec.rb +70 -0
  114. data/spec/lib/granite/form/model/attributes/represents_spec.rb +85 -0
  115. data/spec/lib/granite/form/model/attributes_spec.rb +350 -0
  116. data/spec/lib/granite/form/model/callbacks_spec.rb +337 -0
  117. data/spec/lib/granite/form/model/conventions_spec.rb +11 -0
  118. data/spec/lib/granite/form/model/dirty_spec.rb +84 -0
  119. data/spec/lib/granite/form/model/lifecycle_spec.rb +356 -0
  120. data/spec/lib/granite/form/model/persistence_spec.rb +46 -0
  121. data/spec/lib/granite/form/model/primary_spec.rb +84 -0
  122. data/spec/lib/granite/form/model/representation_spec.rb +139 -0
  123. data/spec/lib/granite/form/model/scopes_spec.rb +86 -0
  124. data/spec/lib/granite/form/model/typecasting_spec.rb +193 -0
  125. data/spec/lib/granite/form/model/validations/associated_spec.rb +102 -0
  126. data/spec/lib/granite/form/model/validations/nested_spec.rb +164 -0
  127. data/spec/lib/granite/form/model/validations_spec.rb +31 -0
  128. data/spec/lib/granite/form/model_spec.rb +10 -0
  129. data/spec/lib/granite/form_spec.rb +11 -0
  130. data/spec/shared/nested_attribute_examples.rb +332 -0
  131. data/spec/spec_helper.rb +50 -0
  132. data/spec/support/model_helpers.rb +10 -0
  133. data/spec/support/muffle_helper.rb +7 -0
  134. metadata +403 -0
@@ -0,0 +1,56 @@
1
+ require 'spec_helper'
2
+
3
+ describe Granite::Form::Model::Attributes::Reflections::Base do
4
+ def reflection(options = {})
5
+ described_class.new(:field, options)
6
+ end
7
+
8
+ describe '.build' do
9
+ before { stub_class(:target) }
10
+
11
+ specify do
12
+ described_class.build(Class.new, Target, :field)
13
+ expect(Target).not_to be_method_defined(:field)
14
+ end
15
+ specify { expect(described_class.build(Class.new, Target, :field).name).to eq('field') }
16
+ end
17
+
18
+ describe '.attribute_class' do
19
+ before do
20
+ stub_class('SomeScope::Borogoves', described_class)
21
+ stub_class('Granite::Form::Model::Attributes::Borogoves')
22
+ end
23
+
24
+ specify { expect(described_class.attribute_class).to eq(Granite::Form::Model::Attributes::Base) }
25
+ specify { expect(SomeScope::Borogoves.attribute_class).to eq(Granite::Form::Model::Attributes::Borogoves) }
26
+ end
27
+
28
+ describe '#name' do
29
+ specify { expect(reflection.name).to eq('field') }
30
+ end
31
+
32
+ describe '#build_attribute' do
33
+ before do
34
+ stub_class('SomeScope::Borogoves', described_class)
35
+ stub_class('Granite::Form::Model::Attributes::Borogoves', Granite::Form::Model::Attributes::Base)
36
+ stub_class(:owner)
37
+ end
38
+
39
+ let(:reflection) { SomeScope::Borogoves.new(:field) }
40
+ let(:owner) { Owner.new }
41
+
42
+ specify { expect(reflection.build_attribute(owner, nil)).to be_a(Granite::Form::Model::Attributes::Borogoves) }
43
+ specify { expect(reflection.build_attribute(owner, nil).name).to eq('field') }
44
+ specify { expect(reflection.build_attribute(owner, nil).owner).to eq(owner) }
45
+ end
46
+
47
+ describe '#type' do
48
+ before { stub_class(:dummy, String) }
49
+
50
+ specify { expect { reflection.type }.to raise_error('Type is not specified for `field`') }
51
+ specify { expect(reflection(type: String).type).to eq(String) }
52
+ specify { expect(reflection(type: :string).type).to eq(String) }
53
+ specify { expect(reflection(type: Dummy).type).to eq(Dummy) }
54
+ specify { expect { reflection(type: :blabla).type }.to raise_error NameError }
55
+ end
56
+ end
@@ -0,0 +1,37 @@
1
+ require 'spec_helper'
2
+
3
+ describe Granite::Form::Model::Attributes::Reflections::Collection do
4
+ def reflection(options = {})
5
+ described_class.new(:field, options)
6
+ end
7
+
8
+ describe '.build' do
9
+ before { stub_class(:target) }
10
+
11
+ specify do
12
+ described_class.build(Class.new, Target, :field)
13
+
14
+ expect(Target).to be_method_defined(:field)
15
+ expect(Target).to be_method_defined(:field=)
16
+ expect(Target).to be_method_defined(:field?)
17
+ expect(Target).to be_method_defined(:field_before_type_cast)
18
+ expect(Target).to be_method_defined(:field_default)
19
+ expect(Target).to be_method_defined(:field_values)
20
+ end
21
+ end
22
+
23
+ describe '#generate_methods' do
24
+ before { stub_class(:target) }
25
+
26
+ specify do
27
+ described_class.generate_methods(:field_alias, Target)
28
+
29
+ expect(Target).to be_method_defined(:field_alias)
30
+ expect(Target).to be_method_defined(:field_alias=)
31
+ expect(Target).to be_method_defined(:field_alias?)
32
+ expect(Target).to be_method_defined(:field_alias_before_type_cast)
33
+ expect(Target).to be_method_defined(:field_alias_default)
34
+ expect(Target).to be_method_defined(:field_alias_values)
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,43 @@
1
+ require 'spec_helper'
2
+
3
+ describe Granite::Form::Model::Attributes::Reflections::Dictionary do
4
+ def reflection(options = {})
5
+ described_class.new(:field, options)
6
+ end
7
+
8
+ describe '.build' do
9
+ before { stub_class(:target) }
10
+
11
+ specify do
12
+ described_class.build(Class.new, Target, :field)
13
+
14
+ expect(Target).to be_method_defined(:field)
15
+ expect(Target).to be_method_defined(:field=)
16
+ expect(Target).to be_method_defined(:field?)
17
+ expect(Target).to be_method_defined(:field_before_type_cast)
18
+ expect(Target).to be_method_defined(:field_default)
19
+ expect(Target).to be_method_defined(:field_values)
20
+ end
21
+ end
22
+
23
+ describe '#generate_methods' do
24
+ before { stub_class(:target) }
25
+
26
+ specify do
27
+ described_class.generate_methods(:field_alias, Target)
28
+
29
+ expect(Target).to be_method_defined(:field_alias)
30
+ expect(Target).to be_method_defined(:field_alias=)
31
+ expect(Target).to be_method_defined(:field_alias?)
32
+ expect(Target).to be_method_defined(:field_alias_before_type_cast)
33
+ expect(Target).to be_method_defined(:field_alias_default)
34
+ expect(Target).to be_method_defined(:field_alias_values)
35
+ end
36
+ end
37
+
38
+ describe '#keys' do
39
+ specify { expect(reflection.keys).to eq([]) }
40
+ specify { expect(reflection(keys: ['a', :b]).keys).to eq(%w[a b]) }
41
+ specify { expect(reflection(keys: :c).keys).to eq(%w[c]) }
42
+ end
43
+ end
@@ -0,0 +1,37 @@
1
+ require 'spec_helper'
2
+
3
+ describe Granite::Form::Model::Attributes::Reflections::Localized do
4
+ def reflection(options = {})
5
+ described_class.new(:field, options)
6
+ end
7
+
8
+ describe '.build' do
9
+ before { stub_class(:target) }
10
+
11
+ specify do
12
+ described_class.build(Class.new, Target, :field)
13
+
14
+ expect(Target).to be_method_defined(:field_translations)
15
+ expect(Target).to be_method_defined(:field_translations=)
16
+ expect(Target).to be_method_defined(:field)
17
+ expect(Target).to be_method_defined(:field=)
18
+ expect(Target).to be_method_defined(:field?)
19
+ expect(Target).to be_method_defined(:field_before_type_cast)
20
+ end
21
+ end
22
+
23
+ describe '#generate_methods' do
24
+ before { stub_class(:target) }
25
+
26
+ specify do
27
+ described_class.generate_methods(:field_alias, Target)
28
+
29
+ expect(Target).to be_method_defined(:field_alias_translations)
30
+ expect(Target).to be_method_defined(:field_alias_translations=)
31
+ expect(Target).to be_method_defined(:field_alias)
32
+ expect(Target).to be_method_defined(:field_alias=)
33
+ expect(Target).to be_method_defined(:field_alias?)
34
+ expect(Target).to be_method_defined(:field_alias_before_type_cast)
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,70 @@
1
+ require 'spec_helper'
2
+
3
+ describe Granite::Form::Model::Attributes::Reflections::Represents do
4
+ def reflection(options = {})
5
+ described_class.new(:field, options.reverse_merge(of: :subject))
6
+ end
7
+
8
+ describe '.build' do
9
+ before { stub_class(:target) }
10
+
11
+ specify do
12
+ described_class.build(Class.new, Target, :field, of: :subject)
13
+
14
+ expect(Target).to be_method_defined(:field)
15
+ expect(Target).to be_method_defined(:field=)
16
+ expect(Target).to be_method_defined(:field?)
17
+ expect(Target).to be_method_defined(:field_before_type_cast)
18
+ expect(Target).to be_method_defined(:field_default)
19
+ expect(Target).to be_method_defined(:field_values)
20
+ end
21
+ end
22
+
23
+ describe '.generate_methods' do
24
+ before { stub_class(:target) }
25
+
26
+ specify do
27
+ described_class.generate_methods(:field_alias, Target)
28
+
29
+ expect(Target).to be_method_defined(:field_alias)
30
+ expect(Target).to be_method_defined(:field_alias=)
31
+ expect(Target).to be_method_defined(:field_alias?)
32
+ expect(Target).to be_method_defined(:field_alias_before_type_cast)
33
+ expect(Target).to be_method_defined(:field_alias_default)
34
+ expect(Target).to be_method_defined(:field_alias_values)
35
+ end
36
+ end
37
+
38
+ describe '#type' do
39
+ specify { expect(reflection.type).to eq(Object) }
40
+ specify { expect(reflection(type: :whatever).type).to eq(Object) }
41
+ end
42
+
43
+ describe '#reference' do
44
+ specify { expect { reflection(of: nil) }.to raise_error ArgumentError }
45
+ specify { expect(reflection(of: :subject).reference).to eq('subject') }
46
+ end
47
+
48
+ describe '#column' do
49
+ specify { expect(reflection.column).to eq('field') }
50
+ specify { expect(reflection(column: 'hello').column).to eq('hello') }
51
+ end
52
+
53
+ describe '#reader' do
54
+ specify { expect(reflection.reader).to eq('field') }
55
+ specify { expect(reflection(column: 'hello').reader).to eq('hello') }
56
+ specify { expect(reflection(reader: 'world').reader).to eq('world') }
57
+ end
58
+
59
+ describe '#reader_before_type_cast' do
60
+ specify { expect(reflection.reader_before_type_cast).to eq('field_before_type_cast') }
61
+ specify { expect(reflection(column: 'hello').reader_before_type_cast).to eq('hello_before_type_cast') }
62
+ specify { expect(reflection(reader: 'world').reader_before_type_cast).to eq('world_before_type_cast') }
63
+ end
64
+
65
+ describe '#writer' do
66
+ specify { expect(reflection.writer).to eq('field=') }
67
+ specify { expect(reflection(column: 'hello').writer).to eq('hello=') }
68
+ specify { expect(reflection(writer: 'world').writer).to eq('world=') }
69
+ end
70
+ end
@@ -0,0 +1,85 @@
1
+ require 'spec_helper'
2
+
3
+ describe Granite::Form::Model::Attributes::Represents do
4
+ before { stub_model(:dummy) }
5
+
6
+ def attribute(*args)
7
+ options = args.extract_options!
8
+ Dummy.add_attribute(Granite::Form::Model::Attributes::Reflections::Represents, :full_name, options.reverse_merge(of: :subject))
9
+ Dummy.new.attribute(:full_name)
10
+ end
11
+
12
+ before do
13
+ stub_model :subject do
14
+ attribute :full_name, String
15
+ end
16
+ end
17
+
18
+ describe '#new' do
19
+ before { attribute(:full_name) }
20
+ let(:attributes) { {foo: 'bar'} }
21
+
22
+ specify { expect { Dummy.new(attributes) }.to_not change { attributes } }
23
+ end
24
+
25
+ describe '#write' do
26
+ subject { Subject.new }
27
+ before { allow_any_instance_of(Dummy).to receive_messages(value: 42, subject: subject) }
28
+ let(:field) { attribute }
29
+
30
+ specify { expect { field.write('hello') }.to change { subject.full_name }.to('hello') }
31
+ end
32
+
33
+ describe '#read' do
34
+ subject { Subject.new(full_name: :hello) }
35
+ before { allow_any_instance_of(Dummy).to receive_messages(value: 42, subject: subject) }
36
+ let(:field) { attribute(normalizer: ->(v) { v && v.is_a?(String) ? v.strip : v }, default: :world, enum: ['hello', '42', 'world', :world]) }
37
+
38
+ specify { expect(field.read).to eq('hello') }
39
+ specify { expect(field.tap { |r| r.write(nil) }.read).to eq(:world) }
40
+ specify { expect(field.tap { |r| r.write(:world) }.read).to eq('world') }
41
+ specify { expect(field.tap { |r| r.write('hello') }.read).to eq('hello') }
42
+ specify { expect(field.tap { |r| r.write(' hello ') }.read).to eq(nil) }
43
+ specify { expect(field.tap { |r| r.write(42) }.read).to eq('42') }
44
+ specify { expect(field.tap { |r| r.write(43) }.read).to eq(nil) }
45
+ specify { expect(field.tap { |r| r.write('') }.read).to eq(nil) }
46
+
47
+ specify { expect { subject.full_name = 42 }.to change { field.read }.to('42') }
48
+
49
+ context ':readonly' do
50
+ specify { expect(attribute(readonly: true).tap { |r| r.write('string') }.read).to eq('hello') }
51
+ end
52
+
53
+ context do
54
+ subject { Subject.new }
55
+ let(:field) { attribute(default: -> { Time.now.to_f }) }
56
+ specify { expect { sleep(0.01) }.not_to change { field.read } }
57
+ end
58
+ end
59
+
60
+ describe '#read_before_type_cast' do
61
+ subject { Subject.new(full_name: :hello) }
62
+ before { allow_any_instance_of(Dummy).to receive_messages(value: 42, subject: subject) }
63
+ let(:field) { attribute(normalizer: ->(v) { v.strip }, default: :world, enum: %w[hello 42 world]) }
64
+
65
+ specify { expect(field.read_before_type_cast).to eq(:hello) }
66
+ specify { expect(field.tap { |r| r.write(nil) }.read_before_type_cast).to eq(:world) }
67
+ specify { expect(field.tap { |r| r.write(:world) }.read_before_type_cast).to eq(:world) }
68
+ specify { expect(field.tap { |r| r.write('hello') }.read_before_type_cast).to eq('hello') }
69
+ specify { expect(field.tap { |r| r.write(42) }.read_before_type_cast).to eq(42) }
70
+ specify { expect(field.tap { |r| r.write(43) }.read_before_type_cast).to eq(43) }
71
+ specify { expect(field.tap { |r| r.write('') }.read_before_type_cast).to eq('') }
72
+
73
+ specify { expect { subject.full_name = 42 }.to change { field.read_before_type_cast }.to(42) }
74
+
75
+ context ':readonly' do
76
+ specify { expect(attribute(readonly: true).tap { |r| r.write('string') }.read_before_type_cast).to eq(:hello) }
77
+ end
78
+
79
+ context do
80
+ subject { Subject.new }
81
+ let(:field) { attribute(default: -> { Time.now.to_f }) }
82
+ specify { expect { sleep(0.01) }.not_to change { field.read_before_type_cast } }
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,350 @@
1
+ require 'spec_helper'
2
+
3
+ describe Granite::Form::Model::Attributes do
4
+ let(:model) do
5
+ stub_model do
6
+ include Granite::Form::Model::Associations
7
+ include Granite::Form::Model::Localization
8
+
9
+ attribute :id, Integer
10
+ attribute :full_name, String
11
+ alias_attribute :name, :full_name
12
+
13
+ localized :t, String
14
+ alias_attribute :title, :t
15
+
16
+ embeds_one(:author) {}
17
+ embeds_many(:projects) {}
18
+ end
19
+ end
20
+
21
+ describe '.reflect_on_attribute' do
22
+ specify { expect(model.reflect_on_attribute(:full_name).name).to eq('full_name') }
23
+ specify { expect(model.reflect_on_attribute('full_name').name).to eq('full_name') }
24
+ specify { expect(model.reflect_on_attribute(:name).name).to eq('full_name') }
25
+ specify { expect(model.reflect_on_attribute(:foobar)).to be_nil }
26
+ end
27
+
28
+ describe '.has_attribute?' do
29
+ specify { expect(model.has_attribute?(:full_name)).to eq(true) }
30
+ specify { expect(model.has_attribute?('full_name')).to eq(true) }
31
+ specify { expect(model.has_attribute?(:name)).to eq(true) }
32
+ specify { expect(model.has_attribute?(:foobar)).to eq(false) }
33
+ end
34
+
35
+ describe '.attribute_names' do
36
+ specify { expect(stub_model.attribute_names).to eq([]) }
37
+ specify { expect(model.attribute_names).to eq(%w[id full_name t author projects]) }
38
+ specify { expect(model.attribute_names(false)).to eq(%w[id full_name t]) }
39
+ end
40
+
41
+ describe '.inspect' do
42
+ specify { expect(stub_model.inspect).to match(/#<Class:0x\w+>\(no attributes\)/) }
43
+ specify { expect(stub_model(:user).inspect).to eq('User(no attributes)') }
44
+ specify do
45
+ expect(stub_model do
46
+ include Granite::Form::Model::Primary
47
+ primary :count, Integer
48
+ attribute :object, Object
49
+ end.inspect).to match(/#<Class:0x\w+>\(\*count: Integer, object: Object\)/) end
50
+ specify do
51
+ expect(stub_model(:user) do
52
+ include Granite::Form::Model::Primary
53
+ primary :count, Integer
54
+ attribute :object, Object
55
+ end.inspect).to match('User(*count: Integer, object: Object)') end
56
+ end
57
+
58
+ describe '#==' do
59
+ let(:model) do
60
+ stub_model do
61
+ attribute :name, String
62
+ attribute :count, Float, default: 0
63
+ end
64
+ end
65
+ subject { model.new name: 'hello', count: 42 }
66
+
67
+ it { is_expected.not_to eq(nil) }
68
+ it { is_expected.not_to eq('hello') }
69
+ it { is_expected.not_to eq(Object.new) }
70
+ it { is_expected.not_to eq(model.new) }
71
+ it { is_expected.not_to eq(model.new(name: 'hello1', count: 42)) }
72
+ it { is_expected.not_to eq(model.new(name: 'hello', count: 42.1)) }
73
+ it { is_expected.to eq(model.new(name: 'hello', count: 42)) }
74
+
75
+ it { is_expected.not_to eql(nil) }
76
+ it { is_expected.not_to eql('hello') }
77
+ it { is_expected.not_to eql(Object.new) }
78
+ it { is_expected.not_to eql(model.new) }
79
+ it { is_expected.not_to eql(model.new(name: 'hello1', count: 42)) }
80
+ it { is_expected.not_to eql(model.new(name: 'hello', count: 42.1)) }
81
+ it { is_expected.to eql(model.new(name: 'hello', count: 42)) }
82
+ end
83
+
84
+ describe '#attribute' do
85
+ let(:instance) { model.new }
86
+ specify { expect(instance.attribute(:full_name).reflection.name).to eq('full_name') }
87
+ specify { expect(instance.attribute('full_name').reflection.name).to eq('full_name') }
88
+ specify { expect(instance.attribute(:name).reflection.name).to eq('full_name') }
89
+ specify { expect(instance.attribute(:foobar)).to be_nil }
90
+
91
+ specify { expect(instance.attribute('full_name')).to equal(instance.attribute(:name)) }
92
+ end
93
+
94
+ describe '#has_attribute?' do
95
+ specify { expect(model.new.has_attribute?(:full_name)).to eq(true) }
96
+ specify { expect(model.new.has_attribute?('full_name')).to eq(true) }
97
+ specify { expect(model.new.has_attribute?(:name)).to eq(true) }
98
+ specify { expect(model.new.has_attribute?(:foobar)).to eq(false) }
99
+ end
100
+
101
+ describe '#attribute_names' do
102
+ specify { expect(stub_model.new.attribute_names).to eq([]) }
103
+ specify { expect(model.new.attribute_names).to eq(%w[id full_name t author projects]) }
104
+ specify { expect(model.new.attribute_names(false)).to eq(%w[id full_name t]) }
105
+ end
106
+
107
+ describe '#attribute_present?' do
108
+ specify { expect(model.new.attribute_present?(:name)).to be(false) }
109
+ specify { expect(model.new(name: '').attribute_present?(:name)).to be(false) }
110
+ specify { expect(model.new(name: 'Name').attribute_present?(:name)).to be(true) }
111
+ end
112
+
113
+ describe '#attributes' do
114
+ specify { expect(stub_model.new.attributes).to eq({}) }
115
+ specify do
116
+ expect(model.new(name: 'Name').attributes)
117
+ .to match('id' => nil, 'full_name' => 'Name', 't' => {}, 'author' => nil, 'projects' => nil)
118
+ end
119
+ specify do
120
+ expect(model.new(name: 'Name').attributes(false))
121
+ .to match('id' => nil, 'full_name' => 'Name', 't' => {})
122
+ end
123
+ end
124
+
125
+ describe '#assign_attributes' do
126
+ let(:attributes) { {id: 42, full_name: 'Name', missed: 'value'} }
127
+ subject { model.new }
128
+
129
+ specify { expect { subject.assign_attributes(attributes) }.to change { subject.id }.to(42) }
130
+ specify { expect { subject.assign_attributes(attributes) }.to change { subject.full_name }.to('Name') }
131
+
132
+ context 'features stack and assign order' do
133
+ let(:model) do
134
+ stub_model do
135
+ attr_reader :logger
136
+
137
+ def self.log(a)
138
+ define_method("#{a}=") do |*args|
139
+ log(a)
140
+ super(*args)
141
+ end
142
+ end
143
+
144
+ def log(o)
145
+ (@logger ||= []).push(o)
146
+ end
147
+
148
+ attribute :plain1, String
149
+ attribute :plain2, String
150
+ log(:plain1)
151
+ log(:plain2)
152
+ end
153
+ end
154
+ subject { model.new }
155
+
156
+ specify do
157
+ expect { subject.assign_attributes(plain1: 'value', plain2: 'value') }
158
+ .to change { subject.logger }.to(%i[plain1 plain2])
159
+ end
160
+
161
+ specify do
162
+ expect { subject.assign_attributes(plain2: 'value', plain1: 'value') }
163
+ .to change { subject.logger }.to(%i[plain2 plain1])
164
+ end
165
+
166
+ context do
167
+ before do
168
+ model.class_eval do
169
+ include Granite::Form::Model::Representation
170
+ include Granite::Form::Model::Associations
171
+
172
+ embeds_one :assoc do
173
+ attribute :assoc_plain, String
174
+ end
175
+ accepts_nested_attributes_for :assoc
176
+
177
+ represents :assoc_plain, of: :assoc
178
+
179
+ log(:assoc_attributes)
180
+ log(:assoc_plain)
181
+
182
+ def assign_attributes(attrs)
183
+ super attrs.merge(attrs.extract!('plain2'))
184
+ end
185
+ end
186
+ end
187
+
188
+ specify do
189
+ expect { subject.assign_attributes(assoc_plain: 'value', assoc_attributes: {}, plain1: 'value', plain2: 'value') }
190
+ .to change { subject.logger }.to(%i[plain1 assoc_attributes assoc_plain plain2])
191
+ end
192
+
193
+ specify do
194
+ expect { subject.assign_attributes(plain1: 'value', plain2: 'value', assoc_plain: 'value', assoc_attributes: {}) }
195
+ .to change { subject.logger }.to(%i[plain1 assoc_attributes assoc_plain plain2])
196
+ end
197
+ end
198
+ end
199
+ end
200
+
201
+ describe '#inspect' do
202
+ specify { expect(stub_model.new.inspect).to match(/#<#<Class:0x\w+> \(no attributes\)>/) }
203
+ specify { expect(stub_model(:user).new.inspect).to match(/#<User \(no attributes\)>/) }
204
+ specify do
205
+ expect(stub_model do
206
+ include Granite::Form::Model::Primary
207
+ primary :count, Integer
208
+ attribute :object, Object
209
+ end.new(object: 'String').inspect).to match(/#<#<Class:0x\w+> \*count: nil, object: "String">/) end
210
+ specify do
211
+ expect(stub_model(:user) do
212
+ include Granite::Form::Model::Primary
213
+ primary :count, Integer
214
+ attribute :object, Object
215
+ end.new.inspect).to match(/#<User \*count: nil, object: nil>/) end
216
+ end
217
+
218
+ context 'attributes integration' do
219
+ let(:model) do
220
+ stub_class do
221
+ include Granite::Form::Model::Attributes
222
+ include Granite::Form::Model::Associations
223
+ attr_accessor :name
224
+
225
+ attribute :id, Integer
226
+ attribute :hello, Object
227
+ attribute :string, String, default: ->(record) { record.name }
228
+ attribute :count, Integer, default: '10'
229
+ attribute(:calc, Integer) { 2 + 3 }
230
+ attribute :enum, Integer, enum: [1, 2, 3]
231
+ attribute :enum_with_default, Integer, enum: [1, 2, 3], default: '2'
232
+ attribute :foo, Boolean, default: false
233
+ collection :array, Integer, enum: [1, 2, 3], default: 7
234
+
235
+ def initialize(name = nil)
236
+ super()
237
+ @name = name
238
+ end
239
+ end
240
+ end
241
+
242
+ subject { model.new('world') }
243
+
244
+ its(:enum_values) { should == [1, 2, 3] }
245
+ its(:string_default) { should == 'world' }
246
+ its(:count_default) { should == '10' }
247
+ its(:name) { should == 'world' }
248
+ its(:hello) { should eq(nil) }
249
+ its(:hello?) { should eq(false) }
250
+ its(:count) { should == 10 }
251
+ its(:count_before_type_cast) { should == '10' }
252
+ its(:count_came_from_user?) { should eq(false) }
253
+ its(:count?) { should eq(true) }
254
+ its(:calc) { should == 5 }
255
+ its(:enum?) { should eq(false) }
256
+ its(:enum_with_default?) { should eq(true) }
257
+ specify { expect { subject.hello = 'worlds' }.to change { subject.hello }.from(nil).to('worlds') }
258
+ specify { expect { subject.count = 20 }.to change { subject.count }.from(10).to(20) }
259
+ specify { expect { subject.calc = 15 }.to change { subject.calc }.from(5).to(15) }
260
+ specify { expect { subject.count = '11' }.to change { subject.count_came_from_user? }.from(false).to(true) }
261
+
262
+ context 'enums' do
263
+ specify do
264
+ subject.enum = 3
265
+ expect(subject.enum).to eq(3)
266
+ end
267
+ specify do
268
+ subject.enum = '3'
269
+ expect(subject.enum).to eq(3)
270
+ end
271
+ specify do
272
+ subject.enum = 10
273
+ expect(subject.enum).to eq(nil)
274
+ end
275
+ specify do
276
+ subject.enum = 'hello'
277
+ expect(subject.enum).to eq(nil)
278
+ end
279
+ specify do
280
+ subject.enum_with_default = 3
281
+ expect(subject.enum_with_default).to eq(3)
282
+ end
283
+ specify do
284
+ subject.enum_with_default = 10
285
+ expect(subject.enum_with_default).to be_nil
286
+ end
287
+ end
288
+
289
+ context 'array' do
290
+ specify do
291
+ subject.array = [2, 4]
292
+ expect(subject.array).to eq([2, nil])
293
+ end
294
+ specify do
295
+ subject.array = [2, 4]
296
+ expect(subject.array?).to eq(true)
297
+ end
298
+ specify do
299
+ subject.array = [2, 4]
300
+ expect(subject.array_values).to eq([1, 2, 3])
301
+ end
302
+ specify do
303
+ subject.array = [2, 4]
304
+ expect(subject.array_default).to eq(7)
305
+ end
306
+ end
307
+
308
+ context 'attribute caching' do
309
+ before do
310
+ subject.hello = 'blabla'
311
+ subject.hello
312
+ subject.hello = 'newnewnew'
313
+ end
314
+
315
+ specify { expect(subject.hello).to eq('newnewnew') }
316
+ end
317
+ end
318
+
319
+ context 'inheritance' do
320
+ let!(:ancestor) do
321
+ Class.new do
322
+ include Granite::Form::Model::Attributes
323
+ attribute :foo, String
324
+ end
325
+ end
326
+
327
+ let!(:descendant1) do
328
+ Class.new ancestor do
329
+ attribute :bar, String
330
+ end
331
+ end
332
+
333
+ let!(:descendant2) do
334
+ Class.new ancestor do
335
+ attribute :baz, String
336
+ attribute :moo, String
337
+ end
338
+ end
339
+
340
+ specify { expect(ancestor._attributes.keys).to eq(['foo']) }
341
+ specify { expect(ancestor.instance_methods).to include :foo, :foo= }
342
+ specify { expect(ancestor.instance_methods).not_to include :bar, :bar=, :baz, :baz= }
343
+ specify { expect(descendant1._attributes.keys).to eq(%w[foo bar]) }
344
+ specify { expect(descendant1.instance_methods).to include :foo, :foo=, :bar, :bar= }
345
+ specify { expect(descendant1.instance_methods).not_to include :baz, :baz= }
346
+ specify { expect(descendant2._attributes.keys).to eq(%w[foo baz moo]) }
347
+ specify { expect(descendant2.instance_methods).to include :foo, :foo=, :baz, :baz=, :moo, :moo= }
348
+ specify { expect(descendant2.instance_methods).not_to include :bar, :bar= }
349
+ end
350
+ end