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