granite-form 0.3.0 → 0.4.0

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 (78) hide show
  1. checksums.yaml +4 -4
  2. data/.github/CODEOWNERS +1 -1
  3. data/CHANGELOG.md +8 -0
  4. data/README.md +6 -13
  5. data/lib/granite/form/model/associations/nested_attributes.rb +2 -2
  6. data/lib/granite/form/model/associations/reflections/embeds_many.rb +1 -1
  7. data/lib/granite/form/model/associations/reflections/embeds_one.rb +11 -1
  8. data/lib/granite/form/model/associations/reflections/references_one.rb +2 -0
  9. data/lib/granite/form/model/associations/reflections/singular.rb +0 -14
  10. data/lib/granite/form/model/attributes/attribute.rb +3 -21
  11. data/lib/granite/form/model/attributes/base.rb +5 -23
  12. data/lib/granite/form/model/attributes/reference_many.rb +1 -1
  13. data/lib/granite/form/model/attributes/reference_one.rb +1 -1
  14. data/lib/granite/form/model/attributes/reflections/attribute.rb +4 -4
  15. data/lib/granite/form/model/attributes/reflections/base/build_type_definition.rb +38 -0
  16. data/lib/granite/form/model/attributes/reflections/base.rb +12 -10
  17. data/lib/granite/form/model/attributes/reflections/collection/build_type_definition.rb +19 -0
  18. data/lib/granite/form/model/attributes/reflections/dictionary/build_type_definition.rb +19 -0
  19. data/lib/granite/form/model/attributes/reflections/dictionary.rb +0 -3
  20. data/lib/granite/form/model/attributes/reflections/represents/build_type_definition.rb +73 -0
  21. data/lib/granite/form/model/attributes/reflections/represents.rb +10 -2
  22. data/lib/granite/form/model/attributes/represents.rb +22 -37
  23. data/lib/granite/form/model/attributes.rb +10 -2
  24. data/lib/granite/form/model/representation.rb +1 -0
  25. data/lib/granite/form/model/validations.rb +6 -0
  26. data/lib/granite/form/model.rb +1 -1
  27. data/lib/granite/form/types/active_support/time_zone.rb +2 -0
  28. data/lib/granite/form/types/array.rb +2 -0
  29. data/lib/granite/form/types/big_decimal.rb +2 -0
  30. data/lib/granite/form/types/boolean.rb +2 -0
  31. data/lib/granite/form/types/collection.rb +11 -0
  32. data/lib/granite/form/types/date.rb +2 -0
  33. data/lib/granite/form/types/date_time.rb +2 -0
  34. data/lib/granite/form/types/dictionary.rb +23 -0
  35. data/lib/granite/form/types/float.rb +2 -0
  36. data/lib/granite/form/types/has_subtype.rb +18 -0
  37. data/lib/granite/form/types/hash_with_action_controller_parameters.rb +2 -0
  38. data/lib/granite/form/types/integer.rb +2 -0
  39. data/lib/granite/form/types/object.rb +28 -0
  40. data/lib/granite/form/types/string.rb +2 -0
  41. data/lib/granite/form/types/time.rb +2 -0
  42. data/lib/granite/form/types/uuid.rb +2 -0
  43. data/lib/granite/form/types.rb +3 -0
  44. data/lib/granite/form/util.rb +55 -0
  45. data/lib/granite/form/version.rb +1 -1
  46. data/lib/granite/form.rb +1 -0
  47. data/spec/granite/form/model/associations/references_many_spec.rb +1 -1
  48. data/spec/granite/form/model/associations/references_one_spec.rb +4 -4
  49. data/spec/granite/form/model/attributes/attribute_spec.rb +0 -29
  50. data/spec/granite/form/model/attributes/reflections/attribute_spec.rb +0 -9
  51. data/spec/granite/form/model/attributes/reflections/base/build_type_definition_spec.rb +27 -0
  52. data/spec/granite/form/model/attributes/reflections/base_spec.rb +16 -10
  53. data/spec/granite/form/model/attributes/reflections/collection/build_type_definition_spec.rb +24 -0
  54. data/spec/granite/form/model/attributes/reflections/dictionary/build_type_definition_spec.rb +24 -0
  55. data/spec/granite/form/model/attributes/reflections/dictionary_spec.rb +0 -6
  56. data/spec/granite/form/model/attributes/reflections/represents/build_type_definition_spec.rb +129 -0
  57. data/spec/granite/form/model/attributes/reflections/represents_spec.rb +43 -20
  58. data/spec/granite/form/model/attributes/represents_spec.rb +78 -55
  59. data/spec/granite/form/model/attributes_spec.rb +84 -23
  60. data/spec/granite/form/model/dirty_spec.rb +0 -6
  61. data/spec/granite/form/model/representation_spec.rb +4 -7
  62. data/spec/granite/form/model/validations_spec.rb +28 -1
  63. data/spec/granite/form/types/collection_spec.rb +22 -0
  64. data/spec/granite/form/types/dictionary_spec.rb +32 -0
  65. data/spec/granite/form/types/has_subtype_spec.rb +20 -0
  66. data/spec/granite/form/types/object_spec.rb +50 -4
  67. data/spec/granite/form/util_spec.rb +108 -0
  68. data/spec/support/active_record.rb +3 -0
  69. metadata +26 -15
  70. data/lib/granite/form/model/attributes/collection.rb +0 -19
  71. data/lib/granite/form/model/attributes/dictionary.rb +0 -28
  72. data/lib/granite/form/model/attributes/localized.rb +0 -44
  73. data/lib/granite/form/model/attributes/reflections/localized.rb +0 -45
  74. data/lib/granite/form/model/localization.rb +0 -26
  75. data/spec/granite/form/model/attributes/collection_spec.rb +0 -72
  76. data/spec/granite/form/model/attributes/dictionary_spec.rb +0 -100
  77. data/spec/granite/form/model/attributes/localized_spec.rb +0 -103
  78. data/spec/granite/form/model/attributes/reflections/localized_spec.rb +0 -37
@@ -0,0 +1,129 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Granite::Form::Model::Attributes::Reflections::Represents::BuildTypeDefinition do
6
+ def build_type_definition(name = :name, **options)
7
+ @reflection = Granite::Form::Model::Attributes::Reflections::Represents.new(name, options.reverse_merge(of: :author))
8
+ described_class.new(owner, @reflection).call
9
+ end
10
+
11
+ def have_type(type)
12
+ have_attributes(type: type, reflection: @reflection, owner: owner)
13
+ end
14
+
15
+ let(:owner) { Owner.new }
16
+
17
+ before do
18
+ stub_model :author
19
+ stub_model(:owner) do
20
+ def author
21
+ @author ||= Author.new
22
+ end
23
+ end
24
+ end
25
+
26
+ it { expect(build_type_definition).to have_type(Object) }
27
+ it { expect(build_type_definition(type: String)).to have_type(String) }
28
+
29
+ context 'when defined in attribute' do
30
+ before { Author.attribute :name, String }
31
+
32
+ it { expect(build_type_definition).to have_type(String) }
33
+ it { expect(build_type_definition(type: Integer)).to have_type(Integer) }
34
+ end
35
+
36
+ context 'when defined in represented attribute' do
37
+ before do
38
+ stub_model(:real_author) do
39
+ attribute :name, Boolean
40
+ end
41
+ Author.class_eval do
42
+ include Granite::Form::Model::Representation
43
+ represents :name, of: :subject
44
+
45
+ def subject
46
+ @subject ||= RealAuthor.new
47
+ end
48
+ end
49
+ end
50
+
51
+ it { expect(build_type_definition).to have_type(Boolean) }
52
+ end
53
+
54
+ context 'when defined in references_many' do
55
+ before do
56
+ stub_class(:user, ActiveRecord::Base)
57
+ Author.class_eval do
58
+ include Granite::Form::Model::Associations
59
+ references_many :users
60
+ end
61
+ end
62
+
63
+ it do
64
+ attribute = build_type_definition(:user_ids)
65
+ expect(attribute).to be_a(Granite::Form::Types::Collection)
66
+ expect(attribute.subtype_definition).to have_type(Integer)
67
+ end
68
+ end
69
+
70
+ context 'when defined in collection' do
71
+ before do
72
+ Author.collection :users, String
73
+ end
74
+
75
+ it do
76
+ attribute = build_type_definition(:users)
77
+ expect(attribute).to be_a(Granite::Form::Types::Collection)
78
+ expect(attribute.subtype_definition).to have_type(String)
79
+ end
80
+ end
81
+
82
+ context 'when defined in dictionary' do
83
+ before do
84
+ Author.dictionary :numbers, Float
85
+ end
86
+
87
+ it do
88
+ attribute = build_type_definition(:numbers)
89
+ expect(attribute).to be_a(Granite::Form::Types::Dictionary)
90
+ expect(attribute.subtype_definition).to have_type(Float)
91
+ end
92
+ end
93
+
94
+ context 'when defined in ActiveRecord::Base' do
95
+ before do
96
+ stub_class(:author, ActiveRecord::Base) do
97
+ alias_attribute :full_name, :name
98
+ end
99
+ end
100
+
101
+ it { expect(build_type_definition).to have_type(String) }
102
+ it { expect(build_type_definition(:status)).to have_type(Integer) }
103
+ it { expect(build_type_definition(:full_name)).to have_type(String) }
104
+ it { expect(build_type_definition(:unknown_attribute)).to have_type(Object) }
105
+ it do
106
+ attribute = build_type_definition(:related_ids)
107
+ expect(attribute).to be_a(Granite::Form::Types::Collection)
108
+ expect(attribute.subtype_definition).to have_type(Integer)
109
+ end
110
+
111
+ context 'with enum' do
112
+ before do
113
+ if ActiveRecord.gem_version >= Gem::Version.new('7.0')
114
+ Author.enum :status, once: 1, many: 2
115
+ else
116
+ Author.enum status: %i[once many]
117
+ end
118
+ end
119
+
120
+ it { expect(build_type_definition(:status)).to have_type(String) }
121
+ end
122
+
123
+ context 'with serialized attribute' do
124
+ before { Author.serialize :data }
125
+
126
+ it { expect(build_type_definition(:data)).to have_type(Object) }
127
+ end
128
+ end
129
+ end
@@ -6,32 +6,55 @@ describe Granite::Form::Model::Attributes::Reflections::Represents do
6
6
  end
7
7
 
8
8
  describe '.build' do
9
- before { stub_class(:target) }
9
+ def build_reflection(column = :name, **options)
10
+ described_class.build(Target, Target, column, of: :subject, **options)
11
+ end
10
12
 
11
- specify do
12
- described_class.build(Class.new, Target, :field, of: :subject)
13
+ before do
14
+ stub_model(:author) do
15
+ attribute :name, String
16
+ attribute :age, Integer
17
+ end
13
18
 
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)
19
+ stub_model(:target) do
20
+ attribute :author, Author
21
+ alias_attribute :subject, :author
22
+ end
20
23
  end
21
- end
22
24
 
23
- describe '.generate_methods' do
24
- before { stub_class(:target) }
25
+ let(:instance) { Target.new attributes }
26
+ let(:attributes) { {subject: Author.new} }
27
+ let!(:reflection) { build_reflection }
28
+
29
+ it { expect(reflection.reference).to eq('author') }
30
+
31
+ it do
32
+ expect(Target).to be_method_defined(:name)
33
+ expect(Target).to be_method_defined(:name=)
34
+ expect(Target).to be_method_defined(:name?)
35
+ expect(Target).to be_method_defined(:name_before_type_cast)
36
+ expect(Target).to be_method_defined(:name_default)
37
+ expect(Target).to be_method_defined(:name_values)
38
+ end
39
+
40
+ it { expect(instance).to be_valid }
41
+
42
+ context 'with missing `of` attribute value' do
43
+ let(:attributes) { super().except(:subject) }
44
+
45
+ it { expect { instance.validate }.to change { instance.errors.messages }.to(author: ["can't be blank"]) }
46
+
47
+ context 'with multiple reflections' do
48
+ before { build_reflection(:age) }
49
+
50
+ it { expect { instance.validate }.to change { instance.errors.messages }.to(author: ["can't be blank"]) }
51
+ end
25
52
 
26
- specify do
27
- described_class.generate_methods(:field_alias, Target)
53
+ context 'when validate_reference is false' do
54
+ let(:reflection) { build_reflection(validate_reference: false) }
28
55
 
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)
56
+ it { expect { instance.validate }.not_to change { instance.errors.messages } }
57
+ end
35
58
  end
36
59
  end
37
60
 
@@ -1,85 +1,108 @@
1
1
  require 'spec_helper'
2
2
 
3
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
4
  before do
13
- stub_model :subject do
14
- attribute :full_name, String
5
+ stub_model :author
6
+ stub_model(:model) do
7
+ def author
8
+ @author ||= Author.new
9
+ end
15
10
  end
16
11
  end
17
12
 
18
- describe '#new' do
19
- before { attribute(:full_name) }
13
+ let(:model) { Model.new }
14
+ let(:attribute) { add_attribute }
15
+
16
+ def add_attribute(*args)
17
+ options = args.extract_options!.reverse_merge(of: :author)
18
+ name = args.first || :name
19
+ reflection = Model.add_attribute(Granite::Form::Model::Attributes::Reflections::Represents, name, options)
20
+ model.attribute(reflection.name)
21
+ end
22
+
23
+ describe '#initialize' do
24
+ before { Author.attribute :name, String, default: 'Default Name' }
25
+
20
26
  let(:attributes) { {foo: 'bar'} }
21
27
 
22
- specify { expect { Dummy.new(attributes) }.to_not change { attributes } }
28
+ it { expect { Model.new(attributes) }.to_not change { attributes } }
29
+
30
+ it { expect(attribute.read).to eq('Default Name') }
31
+ it { expect(attribute.read_before_type_cast).to eq('Default Name') }
32
+
33
+ it { expect(add_attribute(default: -> { 'Field Default' }).read).to eq('Default Name') }
34
+
35
+ context 'when owner changes value after initialize' do
36
+ before do
37
+ attribute
38
+ model.author.name = 'Changed Name'
39
+ end
40
+
41
+ it { expect(attribute.read).to eq('Default Name') }
42
+ it { expect(attribute.read_before_type_cast).to eq('Default Name') }
43
+ end
23
44
  end
24
45
 
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 }
46
+ describe '#sync' do
47
+ before do
48
+ Author.attribute :name, String
49
+ attribute.write('New name')
50
+ end
51
+
52
+ it { expect { attribute.sync }.to change { model.author.name }.from(nil).to('New name') }
53
+
54
+ context 'when represented object does not respond to attribute name' do
55
+ let(:attribute) { add_attribute(:unknown_attribute) }
29
56
 
30
- specify { expect { field.write('hello') }.to change { subject.full_name }.to('hello') }
57
+ it { expect { attribute.sync }.not_to raise_error }
58
+ end
31
59
  end
32
60
 
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]) }
61
+ describe '#changed?' do
62
+ before { Author.attribute :name, Boolean }
37
63
 
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) }
64
+ specify do
65
+ expect(model).to receive(:name_changed?)
66
+ expect(attribute).not_to be_changed
67
+ end
46
68
 
47
- specify { expect { subject.full_name = 42 }.to change { field.read }.to('42') }
69
+ context 'when attribute has default value' do
70
+ let(:attribute) { add_attribute default: -> { true } }
48
71
 
49
- context ':readonly' do
50
- specify { expect(attribute(readonly: true).tap { |r| r.write('string') }.read).to eq('hello') }
72
+ specify do
73
+ expect(model).not_to receive(:name_changed?)
74
+ expect(attribute).to be_changed
75
+ end
51
76
  end
52
77
 
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 } }
78
+ context 'when attribute has false as default value' do
79
+ let(:attribute) { add_attribute default: false }
80
+
81
+ specify do
82
+ expect(model).not_to receive(:name_changed?)
83
+ expect(attribute).to be_changed
84
+ end
57
85
  end
58
86
  end
59
87
 
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]) }
88
+ describe 'typecasting' do
89
+ before { Author.attribute :name, String }
64
90
 
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('') }
91
+ def typecast(value)
92
+ attribute.write(value)
93
+ attribute.read
94
+ end
72
95
 
73
- specify { expect { subject.full_name = 42 }.to change { field.read_before_type_cast }.to(42) }
96
+ it 'returns original value when it has right class' do
97
+ expect(typecast('1')).to eq '1'
98
+ end
74
99
 
75
- context ':readonly' do
76
- specify { expect(attribute(readonly: true).tap { |r| r.write('string') }.read_before_type_cast).to eq(:hello) }
100
+ it 'returns converted value to a proper type' do
101
+ expect(typecast(1)).to eq '1'
77
102
  end
78
103
 
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 } }
104
+ it 'ignores nil' do
105
+ expect(typecast(nil)).to be_nil
83
106
  end
84
107
  end
85
108
  end
@@ -4,15 +4,11 @@ describe Granite::Form::Model::Attributes do
4
4
  let(:model) do
5
5
  stub_model do
6
6
  include Granite::Form::Model::Associations
7
- include Granite::Form::Model::Localization
8
7
 
9
8
  attribute :id, Integer
10
9
  attribute :full_name, String
11
10
  alias_attribute :name, :full_name
12
11
 
13
- localized :t, String
14
- alias_attribute :title, :t
15
-
16
12
  embeds_one(:author) {}
17
13
  embeds_many(:projects) {}
18
14
  end
@@ -34,8 +30,8 @@ describe Granite::Form::Model::Attributes do
34
30
 
35
31
  describe '.attribute_names' do
36
32
  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]) }
33
+ specify { expect(model.attribute_names).to eq(%w[id full_name author projects]) }
34
+ specify { expect(model.attribute_names(false)).to eq(%w[id full_name]) }
39
35
  end
40
36
 
41
37
  describe '.inspect' do
@@ -100,8 +96,8 @@ describe Granite::Form::Model::Attributes do
100
96
 
101
97
  describe '#attribute_names' do
102
98
  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]) }
99
+ specify { expect(model.new.attribute_names).to eq(%w[id full_name author projects]) }
100
+ specify { expect(model.new.attribute_names(false)).to eq(%w[id full_name]) }
105
101
  end
106
102
 
107
103
  describe '#attribute_present?' do
@@ -114,11 +110,11 @@ describe Granite::Form::Model::Attributes do
114
110
  specify { expect(stub_model.new.attributes).to eq({}) }
115
111
  specify do
116
112
  expect(model.new(name: 'Name').attributes)
117
- .to match('id' => nil, 'full_name' => 'Name', 't' => {}, 'author' => nil, 'projects' => nil)
113
+ .to match('id' => nil, 'full_name' => 'Name', 'author' => nil, 'projects' => nil)
118
114
  end
119
115
  specify do
120
116
  expect(model.new(name: 'Name').attributes(false))
121
- .to match('id' => nil, 'full_name' => 'Name', 't' => {})
117
+ .to match('id' => nil, 'full_name' => 'Name')
122
118
  end
123
119
  end
124
120
 
@@ -198,6 +194,35 @@ describe Granite::Form::Model::Attributes do
198
194
  end
199
195
  end
200
196
 
197
+ describe '#sync_attributes' do
198
+ before do
199
+ stub_class :author, ActiveRecord::Base do
200
+ alias_attribute :full_name, :name
201
+ end
202
+
203
+ stub_model :model do
204
+ include Granite::Form::Model::Dirty
205
+ include Granite::Form::Model::Representation
206
+
207
+ attribute :age, Integer
208
+ attribute :author, Author
209
+ represents :name, :full_name, of: :author
210
+ end
211
+ end
212
+
213
+ let(:author) { Author.new }
214
+ let(:model) { Model.new(attributes) }
215
+ let(:attributes) { {author: author, name: 'Author Name', full_name: nil, age: 25} }
216
+
217
+ it { expect { model.sync_attributes }.to change(author, :name).to('Author Name') }
218
+
219
+ context 'with aliased attribute' do
220
+ let(:attributes) { super().merge(name: nil, full_name: 'Name Alias') }
221
+
222
+ it { expect { model.sync_attributes }.to change(author, :name).to('Name Alias') }
223
+ end
224
+ end
225
+
201
226
  describe '#inspect' do
202
227
  specify { expect(stub_model.new.inspect).to match(/#<#<Class:0x\w+> \(no attributes\)>/) }
203
228
  specify { expect(stub_model(:user).new.inspect).to match(/#<User \(no attributes\)>/) }
@@ -218,19 +243,24 @@ describe Granite::Form::Model::Attributes do
218
243
  context 'attributes integration' do
219
244
  let(:model) do
220
245
  stub_class do
246
+ include Granite::Form::Util
221
247
  include Granite::Form::Model::Attributes
222
248
  include Granite::Form::Model::Associations
223
249
  attr_accessor :name
224
250
 
225
251
  attribute :id, Integer
226
252
  attribute :hello, Object
227
- attribute :string, String, default: ->(record) { record.name }
253
+ attribute :string, String, default: -> { name }
228
254
  attribute :count, Integer, default: '10'
229
255
  attribute(:calc, Integer) { 2 + 3 }
230
256
  attribute :enum, Integer, enum: [1, 2, 3]
231
257
  attribute :enum_with_default, Integer, enum: [1, 2, 3], default: '2'
232
258
  attribute :foo, Boolean, default: false
233
- collection :array, Integer, enum: [1, 2, 3], default: 7
259
+ collection :array, Integer, enum: [1, 2, 3], default: [2], normalizer: ->(v) { v.uniq }
260
+ dictionary :dict, Integer, keys: %w[from to], enum: [1, 2, 3], default: {from: 1}, normalizer: proc { |v|
261
+ next v if v[:from].nil? || v[:to].nil? || v[:from] <= v[:to]
262
+ {from: v[:to], to: v[:from]}.with_indifferent_access
263
+ }
234
264
 
235
265
  def initialize(name = nil)
236
266
  super()
@@ -286,23 +316,54 @@ describe Granite::Form::Model::Attributes do
286
316
  end
287
317
  end
288
318
 
289
- context 'array' do
290
- specify do
291
- subject.array = [2, 4]
292
- expect(subject.array).to eq([2, nil])
319
+ describe 'array' do
320
+ def with_assigned_value(value)
321
+ subject.array = value
322
+ expect(subject)
293
323
  end
324
+
294
325
  specify do
295
- subject.array = [2, 4]
296
- expect(subject.array?).to eq(true)
326
+ expect(subject).to have_attributes(
327
+ array: [2],
328
+ array_before_type_cast: [2],
329
+ array?: true,
330
+ array_default: [2],
331
+ array_values: [1, 2, 3]
332
+ )
297
333
  end
298
- specify do
299
- subject.array = [2, 4]
300
- expect(subject.array_values).to eq([1, 2, 3])
334
+
335
+ specify { with_assigned_value(nil).to have_attributes(array: [2], array_before_type_cast: [2]) }
336
+ specify { with_assigned_value([nil]).to have_attributes(array: [nil], array_before_type_cast: [nil]) }
337
+ specify { with_assigned_value(1).to have_attributes(array: [1], array_before_type_cast: 1) }
338
+ specify { with_assigned_value([1, 2]).to have_attributes(array: [1, 2], array_before_type_cast: [1, 2]) }
339
+ specify { with_assigned_value([2, 4]).to have_attributes(array: [2, nil], array_before_type_cast: [2, 4]) }
340
+ specify { with_assigned_value(%w[1 2]).to have_attributes(array: [1, 2], array_before_type_cast: %w[1 2]) }
341
+ specify { with_assigned_value([1, 2, 1]).to have_attributes(array: [1, 2], array_before_type_cast: [1, 2, 1]) }
342
+ end
343
+
344
+ describe 'dict' do
345
+ def with_assigned_value(value)
346
+ subject.dict = value
347
+ expect(subject)
301
348
  end
349
+
302
350
  specify do
303
- subject.array = [2, 4]
304
- expect(subject.array_default).to eq(7)
351
+ expect(subject).to have_attributes(
352
+ dict: {from: 1},
353
+ dict_before_type_cast: {from: 1},
354
+ dict?: true,
355
+ dict_default: {from: 1},
356
+ dict_values: [1, 2, 3]
357
+ )
305
358
  end
359
+
360
+ specify { with_assigned_value(nil).to have_attributes(dict: {'from' => 1}, dict_before_type_cast: {from: 1}) }
361
+ specify { with_assigned_value([nil]).to have_attributes(dict: {}, dict_before_type_cast: [nil]) }
362
+ specify { with_assigned_value(1).to have_attributes(dict: {}, dict_before_type_cast: 1) }
363
+ specify { with_assigned_value(from: 1, to: 2).to have_attributes(dict: {'from' => 1, 'to' => 2}, dict_before_type_cast: {from: 1, to: 2}) }
364
+ specify { with_assigned_value(from: 2, to: 4).to have_attributes(dict: {'from' => 2, 'to' => nil}, dict_before_type_cast: {from: 2, to: 4}) }
365
+ specify { with_assigned_value(from: '1', to: '2').to have_attributes(dict: {'from' => 1, 'to' => 2}, dict_before_type_cast: {from: '1', to: '2'}) }
366
+ specify { with_assigned_value(from: 3, to: 1).to have_attributes(dict: {'from' => 1, 'to' => 3}, dict_before_type_cast: {from: 3, to: 1}) }
306
367
  end
307
368
 
308
369
  context 'attribute caching' do
@@ -5,7 +5,6 @@ describe Granite::Form::Model::Dirty do
5
5
  stub_class(:author, ActiveRecord::Base) {}
6
6
  stub_model :premodel do
7
7
  include Granite::Form::Model::Persistence
8
- include Granite::Form::Model::Localization
9
8
  include Granite::Form::Model::Associations
10
9
 
11
10
  attribute :age, Integer, default: 33
@@ -22,7 +21,6 @@ describe Granite::Form::Model::Dirty do
22
21
  attribute :name, String
23
22
  alias_attribute :n, :name
24
23
  collection :numbers, Integer
25
- localized :title, String
26
24
  end
27
25
  end
28
26
 
@@ -70,10 +68,6 @@ describe Granite::Form::Model::Dirty do
70
68
  specify { expect(Model.new(a: '42').tap { |m| m.update(a: '43') }.changes).to eq('age' => [33, 43]) }
71
69
  specify { expect(Model.new(numbers: '42').changes).to eq('numbers' => [[], [42]]) }
72
70
 
73
- # Have no idea how should it work right now
74
- specify { expect(Model.new(title: 'Hello').changes).to eq('title' => [nil, 'Hello']) }
75
- specify { expect(Model.new(title_translations: {en: 'Hello'}).changes).to eq('title' => [nil, 'Hello']) }
76
-
77
71
  specify { expect(Model.new).not_to respond_to :something_changed? }
78
72
  specify { expect(Model.new).to respond_to :n_changed? }
79
73
 
@@ -23,15 +23,12 @@ describe Granite::Form::Model::Representation do
23
23
  specify { expect(Post.new(author: author).rate).to eq(42) }
24
24
  specify { expect(Post.new(author: author).rate_before_type_cast).to eq('42') }
25
25
  specify { expect(Post.new(rate: '33', author: author).rate).to eq(33) }
26
- specify { expect(Post.new(rate: '33', author: author).author.rate).to eq(33) }
27
26
  specify { expect(Post.new(r: '33', author: author).rate).to eq(33) }
28
- specify { expect(Post.new(r: '33', author: author).author.rate).to eq(33) }
29
27
  specify { expect(Post.new(author: author).rate?).to eq(true) }
30
28
  specify { expect(Post.new(rate: nil, author: author).rate?).to eq(false) }
31
29
 
32
30
  specify { expect(Post.new.rate).to be_nil }
33
31
  specify { expect(Post.new.rate_before_type_cast).to be_nil }
34
- specify { expect { Post.new(rate: '33') }.to raise_error(NoMethodError) }
35
32
 
36
33
  context 'ActionController::Parameters' do
37
34
  let(:params) { instance_double('ActionController::Parameters', to_unsafe_hash: {rate: '33', author: author}) }
@@ -48,12 +45,13 @@ describe Granite::Form::Model::Representation do
48
45
  expect(Post.new(author: author, rate: '33').changes)
49
46
  .to eq('author' => [nil, author], 'rate' => [42, 33])
50
47
 
48
+ author.rate = 33
51
49
  expect(Post.new(author: author, rate: '33').changes)
52
50
  .to eq('author' => [nil, author])
53
51
  end
54
52
  end
55
53
 
56
- context do
54
+ context 'when represents references_one association' do
57
55
  before do
58
56
  stub_class(:author, ActiveRecord::Base)
59
57
 
@@ -71,7 +69,6 @@ describe Granite::Form::Model::Representation do
71
69
  specify { expect(Post.reflect_on_attribute(:name).reference).to eq('author') }
72
70
 
73
71
  specify { expect(Post.new(name: '33', author: author).name).to eq('33') }
74
- specify { expect(Post.new(name: '33', author: author).author.name).to eq('33') }
75
72
  end
76
73
 
77
74
  context 'multiple attributes in a single represents definition' do
@@ -89,11 +86,11 @@ describe Granite::Form::Model::Representation do
89
86
  end
90
87
  end
91
88
 
92
- let(:author) { Author.new(first_name: 'John', last_name: 'Doe') }
89
+ let(:author) { Author.new }
93
90
  let(:post) { Post.new }
94
91
 
95
92
  specify do
96
- expect { post.update(author: author) }
93
+ expect { post.update(first_name: 'John', last_name: 'Doe') }
97
94
  .to change { post.first_name }.to('John')
98
95
  .and change { post.last_name }.to('Doe')
99
96
  end
@@ -1,10 +1,37 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Granite::Form::Model::Validations do
4
+ let!(:add_validations) { model.validates :name, presence: true }
5
+
4
6
  let(:model) do
5
7
  stub_model(:model) do
6
8
  attribute :name, String
7
- validates :name, presence: true
9
+ end
10
+ end
11
+
12
+ before { add_validations }
13
+
14
+ describe '.validates_nested?' do
15
+ subject { model.validates_presence?(:name) }
16
+
17
+ it { is_expected.to be_truthy }
18
+
19
+ context 'when using string name' do
20
+ subject { model.validates_presence?('name') }
21
+
22
+ it { is_expected.to be_truthy }
23
+ end
24
+
25
+ context 'when attribute has no validations' do
26
+ let(:add_validations) {}
27
+
28
+ it { is_expected.to be_falsey }
29
+ end
30
+
31
+ context 'when attribute has different validations' do
32
+ let(:add_validations) { model.validates :name, length: {maximum: 100} }
33
+
34
+ it { is_expected.to be_falsey }
8
35
  end
9
36
  end
10
37