granite-form 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
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