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.
- checksums.yaml +4 -4
- data/.github/CODEOWNERS +1 -1
- data/CHANGELOG.md +8 -0
- data/README.md +6 -13
- data/lib/granite/form/model/associations/nested_attributes.rb +2 -2
- data/lib/granite/form/model/associations/reflections/embeds_many.rb +1 -1
- data/lib/granite/form/model/associations/reflections/embeds_one.rb +11 -1
- data/lib/granite/form/model/associations/reflections/references_one.rb +2 -0
- data/lib/granite/form/model/associations/reflections/singular.rb +0 -14
- data/lib/granite/form/model/attributes/attribute.rb +3 -21
- data/lib/granite/form/model/attributes/base.rb +5 -23
- data/lib/granite/form/model/attributes/reference_many.rb +1 -1
- data/lib/granite/form/model/attributes/reference_one.rb +1 -1
- data/lib/granite/form/model/attributes/reflections/attribute.rb +4 -4
- data/lib/granite/form/model/attributes/reflections/base/build_type_definition.rb +38 -0
- data/lib/granite/form/model/attributes/reflections/base.rb +12 -10
- data/lib/granite/form/model/attributes/reflections/collection/build_type_definition.rb +19 -0
- data/lib/granite/form/model/attributes/reflections/dictionary/build_type_definition.rb +19 -0
- data/lib/granite/form/model/attributes/reflections/dictionary.rb +0 -3
- data/lib/granite/form/model/attributes/reflections/represents/build_type_definition.rb +73 -0
- data/lib/granite/form/model/attributes/reflections/represents.rb +10 -2
- data/lib/granite/form/model/attributes/represents.rb +22 -37
- data/lib/granite/form/model/attributes.rb +10 -2
- data/lib/granite/form/model/representation.rb +1 -0
- data/lib/granite/form/model/validations.rb +6 -0
- data/lib/granite/form/model.rb +1 -1
- data/lib/granite/form/types/active_support/time_zone.rb +2 -0
- data/lib/granite/form/types/array.rb +2 -0
- data/lib/granite/form/types/big_decimal.rb +2 -0
- data/lib/granite/form/types/boolean.rb +2 -0
- data/lib/granite/form/types/collection.rb +11 -0
- data/lib/granite/form/types/date.rb +2 -0
- data/lib/granite/form/types/date_time.rb +2 -0
- data/lib/granite/form/types/dictionary.rb +23 -0
- data/lib/granite/form/types/float.rb +2 -0
- data/lib/granite/form/types/has_subtype.rb +18 -0
- data/lib/granite/form/types/hash_with_action_controller_parameters.rb +2 -0
- data/lib/granite/form/types/integer.rb +2 -0
- data/lib/granite/form/types/object.rb +28 -0
- data/lib/granite/form/types/string.rb +2 -0
- data/lib/granite/form/types/time.rb +2 -0
- data/lib/granite/form/types/uuid.rb +2 -0
- data/lib/granite/form/types.rb +3 -0
- data/lib/granite/form/util.rb +55 -0
- data/lib/granite/form/version.rb +1 -1
- data/lib/granite/form.rb +1 -0
- data/spec/granite/form/model/associations/references_many_spec.rb +1 -1
- data/spec/granite/form/model/associations/references_one_spec.rb +4 -4
- data/spec/granite/form/model/attributes/attribute_spec.rb +0 -29
- data/spec/granite/form/model/attributes/reflections/attribute_spec.rb +0 -9
- data/spec/granite/form/model/attributes/reflections/base/build_type_definition_spec.rb +27 -0
- data/spec/granite/form/model/attributes/reflections/base_spec.rb +16 -10
- data/spec/granite/form/model/attributes/reflections/collection/build_type_definition_spec.rb +24 -0
- data/spec/granite/form/model/attributes/reflections/dictionary/build_type_definition_spec.rb +24 -0
- data/spec/granite/form/model/attributes/reflections/dictionary_spec.rb +0 -6
- data/spec/granite/form/model/attributes/reflections/represents/build_type_definition_spec.rb +129 -0
- data/spec/granite/form/model/attributes/reflections/represents_spec.rb +43 -20
- data/spec/granite/form/model/attributes/represents_spec.rb +78 -55
- data/spec/granite/form/model/attributes_spec.rb +84 -23
- data/spec/granite/form/model/dirty_spec.rb +0 -6
- data/spec/granite/form/model/representation_spec.rb +4 -7
- data/spec/granite/form/model/validations_spec.rb +28 -1
- data/spec/granite/form/types/collection_spec.rb +22 -0
- data/spec/granite/form/types/dictionary_spec.rb +32 -0
- data/spec/granite/form/types/has_subtype_spec.rb +20 -0
- data/spec/granite/form/types/object_spec.rb +50 -4
- data/spec/granite/form/util_spec.rb +108 -0
- data/spec/support/active_record.rb +3 -0
- metadata +26 -15
- data/lib/granite/form/model/attributes/collection.rb +0 -19
- data/lib/granite/form/model/attributes/dictionary.rb +0 -28
- data/lib/granite/form/model/attributes/localized.rb +0 -44
- data/lib/granite/form/model/attributes/reflections/localized.rb +0 -45
- data/lib/granite/form/model/localization.rb +0 -26
- data/spec/granite/form/model/attributes/collection_spec.rb +0 -72
- data/spec/granite/form/model/attributes/dictionary_spec.rb +0 -100
- data/spec/granite/form/model/attributes/localized_spec.rb +0 -103
- 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
|
-
|
9
|
+
def build_reflection(column = :name, **options)
|
10
|
+
described_class.build(Target, Target, column, of: :subject, **options)
|
11
|
+
end
|
10
12
|
|
11
|
-
|
12
|
-
|
13
|
+
before do
|
14
|
+
stub_model(:author) do
|
15
|
+
attribute :name, String
|
16
|
+
attribute :age, Integer
|
17
|
+
end
|
13
18
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
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
|
-
|
24
|
-
|
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
|
-
|
27
|
-
|
53
|
+
context 'when validate_reference is false' do
|
54
|
+
let(:reflection) { build_reflection(validate_reference: false) }
|
28
55
|
|
29
|
-
|
30
|
-
|
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 :
|
14
|
-
|
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
|
-
|
19
|
-
|
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
|
-
|
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 '#
|
26
|
-
|
27
|
-
|
28
|
-
|
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
|
-
|
57
|
+
it { expect { attribute.sync }.not_to raise_error }
|
58
|
+
end
|
31
59
|
end
|
32
60
|
|
33
|
-
describe '#
|
34
|
-
|
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
|
39
|
-
|
40
|
-
|
41
|
-
|
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
|
-
|
69
|
+
context 'when attribute has default value' do
|
70
|
+
let(:attribute) { add_attribute default: -> { true } }
|
48
71
|
|
49
|
-
|
50
|
-
|
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
|
-
|
55
|
-
|
56
|
-
specify
|
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 '
|
61
|
-
|
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
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
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
|
-
|
96
|
+
it 'returns original value when it has right class' do
|
97
|
+
expect(typecast('1')).to eq '1'
|
98
|
+
end
|
74
99
|
|
75
|
-
|
76
|
-
|
100
|
+
it 'returns converted value to a proper type' do
|
101
|
+
expect(typecast(1)).to eq '1'
|
77
102
|
end
|
78
103
|
|
79
|
-
|
80
|
-
|
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
|
38
|
-
specify { expect(model.attribute_names(false)).to eq(%w[id full_name
|
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
|
104
|
-
specify { expect(model.new.attribute_names(false)).to eq(%w[id full_name
|
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', '
|
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'
|
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: ->
|
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:
|
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
|
-
|
290
|
-
|
291
|
-
subject.array =
|
292
|
-
expect(subject
|
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.
|
296
|
-
|
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
|
-
|
299
|
-
|
300
|
-
|
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.
|
304
|
-
|
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
|
89
|
+
let(:author) { Author.new }
|
93
90
|
let(:post) { Post.new }
|
94
91
|
|
95
92
|
specify do
|
96
|
-
expect { post.update(
|
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
|
-
|
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
|
|