granite-form 0.4.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (109) hide show
  1. checksums.yaml +4 -4
  2. data/.github/CODEOWNERS +1 -1
  3. data/.github/workflows/ruby.yml +3 -5
  4. data/.rubocop.yml +26 -48
  5. data/.rubocop_todo.yml +304 -27
  6. data/Appraisals +1 -1
  7. data/CHANGELOG.md +15 -2
  8. data/gemfiles/{rails.5.0.gemfile → rails.7.1.gemfile} +3 -3
  9. data/granite-form.gemspec +6 -5
  10. data/lib/granite/form/active_record/associations.rb +4 -3
  11. data/lib/granite/form/config.rb +1 -1
  12. data/lib/granite/form/errors.rb +34 -32
  13. data/lib/granite/form/extensions.rb +2 -1
  14. data/lib/granite/form/model/associations/base.rb +6 -2
  15. data/lib/granite/form/model/associations/collection/embedded.rb +1 -1
  16. data/lib/granite/form/model/associations/collection/proxy.rb +3 -3
  17. data/lib/granite/form/model/associations/embeds_any.rb +1 -1
  18. data/lib/granite/form/model/associations/embeds_many.rb +15 -11
  19. data/lib/granite/form/model/associations/embeds_one.rb +9 -8
  20. data/lib/granite/form/model/associations/nested_attributes.rb +60 -32
  21. data/lib/granite/form/model/associations/persistence_adapters/active_record/referenced_proxy.rb +2 -1
  22. data/lib/granite/form/model/associations/persistence_adapters/active_record.rb +7 -6
  23. data/lib/granite/form/model/associations/persistence_adapters/base.rb +8 -4
  24. data/lib/granite/form/model/associations/references_any.rb +1 -1
  25. data/lib/granite/form/model/associations/references_many.rb +3 -2
  26. data/lib/granite/form/model/associations/references_one.rb +1 -1
  27. data/lib/granite/form/model/associations/reflections/base.rb +3 -2
  28. data/lib/granite/form/model/associations/reflections/embeds_any.rb +4 -4
  29. data/lib/granite/form/model/associations/reflections/embeds_many.rb +4 -1
  30. data/lib/granite/form/model/associations/reflections/embeds_one.rb +4 -1
  31. data/lib/granite/form/model/associations/reflections/references_any.rb +6 -6
  32. data/lib/granite/form/model/associations/reflections/references_many.rb +1 -1
  33. data/lib/granite/form/model/associations/reflections/references_one.rb +1 -1
  34. data/lib/granite/form/model/associations/validations.rb +6 -6
  35. data/lib/granite/form/model/associations.rb +6 -4
  36. data/lib/granite/form/model/attributes/attribute.rb +1 -0
  37. data/lib/granite/form/model/attributes/base.rb +9 -7
  38. data/lib/granite/form/model/attributes/reference_one.rb +1 -1
  39. data/lib/granite/form/model/attributes/reflections/base/build_type_definition.rb +2 -1
  40. data/lib/granite/form/model/attributes/reflections/represents/build_type_definition.rb +2 -2
  41. data/lib/granite/form/model/attributes/represents.rb +1 -1
  42. data/lib/granite/form/model/attributes.rb +21 -13
  43. data/lib/granite/form/model/conventions.rb +1 -1
  44. data/lib/granite/form/model/persistence.rb +1 -1
  45. data/lib/granite/form/model/primary.rb +1 -1
  46. data/lib/granite/form/model/representation.rb +4 -4
  47. data/lib/granite/form/model/scopes.rb +5 -5
  48. data/lib/granite/form/model/validations.rb +4 -3
  49. data/lib/granite/form/types/active_support/time_zone.rb +1 -1
  50. data/lib/granite/form/types/array.rb +1 -1
  51. data/lib/granite/form/types/big_decimal.rb +1 -1
  52. data/lib/granite/form/types/boolean.rb +1 -1
  53. data/lib/granite/form/types/date.rb +1 -1
  54. data/lib/granite/form/types/date_time.rb +1 -1
  55. data/lib/granite/form/types/dictionary.rb +1 -1
  56. data/lib/granite/form/types/float.rb +1 -1
  57. data/lib/granite/form/types/has_subtype.rb +1 -0
  58. data/lib/granite/form/types/hash_with_action_controller_parameters.rb +2 -2
  59. data/lib/granite/form/types/integer.rb +1 -1
  60. data/lib/granite/form/types/object.rb +2 -1
  61. data/lib/granite/form/types/string.rb +1 -1
  62. data/lib/granite/form/types/time.rb +1 -1
  63. data/lib/granite/form/types/uuid.rb +1 -1
  64. data/lib/granite/form/util.rb +1 -1
  65. data/lib/granite/form/version.rb +1 -1
  66. data/spec/granite/form/active_record/associations_spec.rb +35 -13
  67. data/spec/granite/form/config_spec.rb +8 -4
  68. data/spec/granite/form/model/associations/embeds_many_spec.rb +99 -51
  69. data/spec/granite/form/model/associations/embeds_one_spec.rb +48 -25
  70. data/spec/granite/form/model/associations/persistence_adapters/active_record_spec.rb +12 -7
  71. data/spec/granite/form/model/associations/references_many_spec.rb +51 -10
  72. data/spec/granite/form/model/associations/references_one_spec.rb +17 -6
  73. data/spec/granite/form/model/associations/reflections/embeds_many_spec.rb +51 -16
  74. data/spec/granite/form/model/associations/reflections/embeds_one_spec.rb +19 -9
  75. data/spec/granite/form/model/associations/reflections/references_many_spec.rb +67 -15
  76. data/spec/granite/form/model/associations/reflections/references_one_spec.rb +34 -11
  77. data/spec/granite/form/model/associations/validations_spec.rb +16 -5
  78. data/spec/granite/form/model/associations_spec.rb +28 -9
  79. data/spec/granite/form/model/attributes/attribute_spec.rb +33 -11
  80. data/spec/granite/form/model/attributes/base_spec.rb +26 -2
  81. data/spec/granite/form/model/attributes/reflections/attribute_spec.rb +1 -0
  82. data/spec/granite/form/model/attributes/reflections/base_spec.rb +1 -0
  83. data/spec/granite/form/model/attributes/reflections/represents/build_type_definition_spec.rb +3 -1
  84. data/spec/granite/form/model/attributes/reflections/represents_spec.rb +2 -2
  85. data/spec/granite/form/model/attributes/represents_spec.rb +2 -2
  86. data/spec/granite/form/model/attributes_spec.rb +76 -36
  87. data/spec/granite/form/model/dirty_spec.rb +3 -0
  88. data/spec/granite/form/model/persistence_spec.rb +15 -5
  89. data/spec/granite/form/model/primary_spec.rb +17 -2
  90. data/spec/granite/form/model/representation_spec.rb +13 -3
  91. data/spec/granite/form/model/scopes_spec.rb +8 -3
  92. data/spec/granite/form/model/validations/associated_spec.rb +20 -6
  93. data/spec/granite/form/model/validations/nested_spec.rb +30 -14
  94. data/spec/granite/form/model/validations_spec.rb +1 -1
  95. data/spec/granite/form/model_spec.rb +1 -0
  96. data/spec/granite/form/types/collection_spec.rb +2 -1
  97. data/spec/granite/form/types/date_spec.rb +1 -1
  98. data/spec/granite/form/types/date_time_spec.rb +0 -2
  99. data/spec/granite/form/types/dictionary_spec.rb +1 -0
  100. data/spec/granite/form/types/has_subtype_spec.rb +6 -1
  101. data/spec/granite/form/types/hash_with_action_controller_parameters_spec.rb +1 -1
  102. data/spec/granite/form/types/object_spec.rb +2 -0
  103. data/spec/granite/form/types/time_spec.rb +0 -2
  104. data/spec/granite/form/util_spec.rb +6 -3
  105. data/spec/support/active_record.rb +13 -0
  106. data/spec/support/shared/nested_attribute_examples.rb +110 -54
  107. metadata +28 -16
  108. data/gemfiles/rails.5.1.gemfile +0 -14
  109. data/gemfiles/rails.5.2.gemfile +0 -14
@@ -34,9 +34,18 @@ describe Granite::Form::Model::Associations do
34
34
 
35
35
  describe '#reflect_on_association' do
36
36
  specify { expect(Nobody.reflect_on_association(:blabla)).to be_nil }
37
- specify { expect(Admin.reflect_on_association('projects')).to be_a Granite::Form::Model::Associations::Reflections::EmbedsMany }
37
+
38
+ specify do
39
+ expect(Admin.reflect_on_association('projects'))
40
+ .to be_a Granite::Form::Model::Associations::Reflections::EmbedsMany
41
+ end
42
+
38
43
  specify { expect(Admin.reflect_on_association('own_projects').name).to eq(:admin_projects) }
39
- specify { expect(Manager.reflect_on_association(:managed_project)).to be_a Granite::Form::Model::Associations::Reflections::EmbedsOne }
44
+
45
+ specify do
46
+ expect(Manager.reflect_on_association(:managed_project))
47
+ .to be_a Granite::Form::Model::Associations::Reflections::EmbedsOne
48
+ end
40
49
  end
41
50
  end
42
51
 
@@ -47,7 +56,8 @@ describe Granite::Form::Model::Associations do
47
56
  include Granite::Form::Model::Associations
48
57
 
49
58
  embeds_one :author, class_name: 'Borogoves'
50
- end.reflect_on_association(:author).data_source end.to raise_error NameError
59
+ end.reflect_on_association(:author).data_source
60
+ end.to raise_error NameError
51
61
  end
52
62
 
53
63
  specify do
@@ -58,7 +68,8 @@ describe Granite::Form::Model::Associations do
58
68
  embeds_many :projects, class_name: 'Borogoves' do
59
69
  attribute :title
60
70
  end
61
- end.reflect_on_association(:projects).data_source end.to raise_error NameError
71
+ end.reflect_on_association(:projects).data_source
72
+ end.to raise_error NameError
62
73
  end
63
74
  end
64
75
 
@@ -108,7 +119,9 @@ describe Granite::Form::Model::Associations do
108
119
  specify { expect(user.profile).to be_nil }
109
120
 
110
121
  describe '.inspect' do
111
- specify { expect(User.inspect).to eq('User(profile: EmbedsOne(Profile), projects: EmbedsMany(Project), login: Object)') }
122
+ specify do
123
+ expect(User.inspect).to eq('User(profile: EmbedsOne(Profile), projects: EmbedsMany(Project), login: Object)')
124
+ end
112
125
  end
113
126
 
114
127
  describe '.association_names' do
@@ -118,9 +131,10 @@ describe Granite::Form::Model::Associations do
118
131
  describe '#inspect' do
119
132
  let(:profile) { Profile.new first_name: 'Name' }
120
133
  let(:project) { Project.new title: 'Project' }
134
+
121
135
  specify do
122
136
  expect(User.new(login: 'Login', profile: profile, projects: [project]).inspect)
123
- .to eq('#<User profile: #<EmbedsOne #<Profile first_name: "Name", last_name: nil>>, projects: #<EmbedsMany [#<Project author: #<EmbedsOne nil>, title: "P...]>, login: "Login">')
137
+ .to eq('#<User profile: #<EmbedsOne #<Profile first_name: "Name", last_name: nil>>, projects: #<EmbedsMany [#<Project author: #<EmbedsOne nil>, title: "P...]>, login: "Login">') # rubocop:disable Layout/LineLength
124
138
  end
125
139
  end
126
140
 
@@ -137,7 +151,8 @@ describe Granite::Form::Model::Associations do
137
151
  specify { expect(User.new(projects: [project])).not_to eql(User.new) }
138
152
 
139
153
  context do
140
- before { User.send(:include, Granite::Form::Model::Primary) }
154
+ before { User.include Granite::Form::Model::Primary }
155
+
141
156
  let(:user) { User.new(projects: [project]) }
142
157
 
143
158
  specify { expect(user).to eq(user.clone.tap { |b| b.projects(author: project) }) }
@@ -161,13 +176,17 @@ describe Granite::Form::Model::Associations do
161
176
  end
162
177
 
163
178
  describe '#instantiate' do
164
- before { User.send(:include, Granite::Form::Model::Persistence) }
179
+ before do
180
+ User.include Granite::Form::Model::Persistence
181
+ project.build_author(name: 'Author')
182
+ end
183
+
165
184
  let(:profile) { Profile.new first_name: 'Name' }
166
185
  let(:project) { Project.new title: 'Project' }
167
186
  let(:user) { User.new(profile: profile, projects: [project]) }
168
- before { project.build_author(name: 'Author') }
169
187
 
170
188
  specify { expect(User.instantiate(JSON.parse(user.to_json))).to eq(user) }
189
+
171
190
  specify do
172
191
  expect(User.instantiate(JSON.parse(user.to_json))
173
192
  .tap { |u| u.projects.first.author.name = 'Other' }).not_to eq(user)
@@ -5,12 +5,16 @@ describe Granite::Form::Model::Attributes::Attribute do
5
5
 
6
6
  def attribute(*args)
7
7
  options = args.extract_options!
8
- Dummy.add_attribute(Granite::Form::Model::Attributes::Reflections::Attribute, :field, {type: Object}.merge(options))
8
+ Dummy.add_attribute(Granite::Form::Model::Attributes::Reflections::Attribute, :field,
9
+ { type: Object }.merge(options))
9
10
  Dummy.new.attribute(:field)
10
11
  end
11
12
 
12
13
  describe '#read' do
13
- let(:field) { attribute(type: String, normalizer: ->(v) { v ? v.strip : v }, default: :world, enum: %w[hello 42 world]) }
14
+ let(:field) do
15
+ normalizer = ->(v) { v ? v.strip : v }
16
+ attribute(type: String, normalizer: normalizer, default: :world, enum: %w[hello 42 world])
17
+ end
14
18
 
15
19
  specify { expect(field.tap { |r| r.write(nil) }.read).to eq('world') }
16
20
  specify { expect(field.tap { |r| r.write(:world) }.read).to eq('world') }
@@ -36,7 +40,11 @@ describe Granite::Form::Model::Attributes::Attribute do
36
40
  specify { expect(field.tap { |r| r.write('') }.read_before_type_cast).to eq('') }
37
41
 
38
42
  context ':readonly' do
39
- specify { expect(attribute(readonly: true, default: :world).tap { |r| r.write('string') }.read_before_type_cast).to eq(:world) }
43
+ specify do
44
+ attr = attribute(readonly: true, default: :world)
45
+ attr.write('string')
46
+ expect(attr.read_before_type_cast).to eq(:world)
47
+ end
40
48
  end
41
49
  end
42
50
 
@@ -58,11 +66,22 @@ describe Granite::Form::Model::Attributes::Attribute do
58
66
  describe '#normalize' do
59
67
  specify { expect(attribute.normalize(' hello ')).to eq(' hello ') }
60
68
  specify { expect(attribute(normalizer: ->(v) { v.strip }).normalize(' hello ')).to eq('hello') }
61
- specify { expect(attribute(normalizer: [->(v) { v.strip }, ->(v) { v.first(4) }]).normalize(' hello ')).to eq('hell') }
62
- specify { expect(attribute(normalizer: [->(v) { v.first(4) }, ->(v) { v.strip }]).normalize(' hello ')).to eq('hel') }
69
+
70
+ specify do
71
+ normalizers = [->(v) { v.strip }, ->(v) { v.first(4) }]
72
+ attr = attribute(normalizer: normalizers)
73
+ expect(attr.normalize(' hello ')).to eq('hell')
74
+ end
75
+
76
+ specify do
77
+ normalizers = [->(v) { v.first(4) }, ->(v) { v.strip }]
78
+ attr = attribute(normalizer: normalizers)
79
+ expect(attr.normalize(' hello ')).to eq('hel')
80
+ end
63
81
 
64
82
  context do
65
83
  before { allow_any_instance_of(Dummy).to receive_messages(value: 'value') }
84
+
66
85
  let(:other) { 'other' }
67
86
 
68
87
  specify { expect(attribute(normalizer: ->(_v) { value }).normalize(' hello ')).to eq('value') }
@@ -89,12 +108,14 @@ describe Granite::Form::Model::Attributes::Attribute do
89
108
  specify { expect(attribute(normalizer: :strip).normalize(' hello ')).to eq('hello') }
90
109
  specify { expect(attribute(normalizer: %i[strip trim]).normalize(' hello ')).to eq('he') }
91
110
  specify { expect(attribute(normalizer: %i[trim strip]).normalize(' hello ')).to eq('h') }
92
- specify { expect(attribute(normalizer: [:strip, {trim: {length: 4}}]).normalize(' hello ')).to eq('hell') }
93
- specify { expect(attribute(normalizer: {strip: {}, trim: {length: 4}}).normalize(' hello ')).to eq('hell') }
111
+ specify { expect(attribute(normalizer: [:strip, { trim: { length: 4 } }]).normalize(' hello ')).to eq('hell') }
112
+ specify { expect(attribute(normalizer: { strip: {}, trim: { length: 4 } }).normalize(' hello ')).to eq('hell') }
113
+
94
114
  specify do
95
- expect(attribute(normalizer: [:strip, {trim: {length: 4}}, ->(v) { v.last(2) }])
115
+ expect(attribute(normalizer: [:strip, { trim: { length: 4 } }, ->(v) { v.last(2) }])
96
116
  .normalize(' hello ')).to eq('ll')
97
117
  end
118
+
98
119
  specify { expect(attribute(normalizer: :reset).normalize('')).to eq(nil) }
99
120
  specify { expect(attribute(normalizer: %i[strip reset]).normalize(' ')).to eq(nil) }
100
121
  specify { expect(attribute(normalizer: :reset, default: '!!!').normalize(nil)).to eq('!!!') }
@@ -103,10 +124,11 @@ describe Granite::Form::Model::Attributes::Attribute do
103
124
  context do
104
125
  let(:length) { 3 }
105
126
 
106
- specify { expect(attribute(normalizer: [:strip, {trim: {length: 4}}]).normalize(' hello ')).to eq('hel') }
107
- specify { expect(attribute(normalizer: {strip: {}, trim: {length: 4}}).normalize(' hello ')).to eq('hel') }
127
+ specify { expect(attribute(normalizer: [:strip, { trim: { length: 4 } }]).normalize(' hello ')).to eq('hel') }
128
+ specify { expect(attribute(normalizer: { strip: {}, trim: { length: 4 } }).normalize(' hello ')).to eq('hel') }
129
+
108
130
  specify do
109
- expect(attribute(normalizer: [:strip, {trim: {length: 4}}, ->(v) { v.last(2) }])
131
+ expect(attribute(normalizer: [:strip, { trim: { length: 4 } }, ->(v) { v.last(2) }])
110
132
  .normalize(' hello ')).to eq('el')
111
133
  end
112
134
  end
@@ -7,12 +7,16 @@ describe Granite::Form::Model::Attributes::Base do
7
7
 
8
8
  def attribute(*args)
9
9
  options = args.extract_options!
10
- Dummy.add_attribute(Granite::Form::Model::Attributes::Reflections::Base, :field, options.reverse_merge(type: Object))
10
+ Dummy.add_attribute(Granite::Form::Model::Attributes::Reflections::Base, :field,
11
+ options.reverse_merge(type: Object))
11
12
  model.attribute(:field)
12
13
  end
13
14
 
14
15
  describe '#read' do
15
- let(:field) { attribute(type: String, normalizer: ->(v) { v ? v.strip : v }, default: :world, enum: %w[hello 42 world]) }
16
+ let(:field) do
17
+ normalizer = ->(v) { v ? v.strip : v }
18
+ attribute(type: String, normalizer: normalizer, default: :world, enum: %w[hello 42 world])
19
+ end
16
20
  let(:object) { Object.new }
17
21
 
18
22
  specify { expect(field.tap { |r| r.write(nil) }.read).to be_nil }
@@ -99,8 +103,28 @@ describe Granite::Form::Model::Attributes::Base do
99
103
 
100
104
  describe '#type_definition' do
101
105
  subject { attr.type_definition }
106
+
102
107
  let(:attr) { attribute(type: String) }
103
108
 
104
109
  it { is_expected.to have_attributes(type: String, reflection: subject.reflection, owner: model) }
105
110
  end
111
+
112
+ describe '#inspect_attribute' do
113
+ let(:field) { attribute(type: type) }
114
+ let(:object) { Object.new }
115
+
116
+ {
117
+ 'hello' => 'field: "hello"',
118
+ 123 => 'field: 123',
119
+ Date.new(2023, 6, 20) => 'field: "2023-06-20"',
120
+ DateTime.new(2023, 6, 20, 12, 30) => 'field: "2023-06-20 12:30:00"',
121
+ Time.new(2023, 6, 20, 12, 30) => 'field: "2023-06-20 12:30:00"'
122
+ }.each do |input, expected_output|
123
+ context "attribute type is #{input.class}" do
124
+ let(:type) { input.class }
125
+
126
+ specify { expect(field.tap { |r| r.write(input) }.inspect_attribute).to eq(expected_output) }
127
+ end
128
+ end
129
+ end
106
130
  end
@@ -10,6 +10,7 @@ describe Granite::Form::Model::Attributes::Reflections::Attribute do
10
10
 
11
11
  specify { expect(described_class.build(Class.new, Target, :field, String).type).to eq(String) }
12
12
  specify { expect(described_class.build(Class.new, Target, :field) {}.defaultizer).to be_a(Proc) }
13
+
13
14
  specify do
14
15
  described_class.build(Class.new, Target, :field)
15
16
 
@@ -12,6 +12,7 @@ describe Granite::Form::Model::Attributes::Reflections::Base do
12
12
  described_class.build(Class.new, Target, :field)
13
13
  expect(Target).not_to be_method_defined(:field)
14
14
  end
15
+
15
16
  specify { expect(described_class.build(Class.new, Target, :field).name).to eq('field') }
16
17
  end
17
18
 
@@ -4,7 +4,8 @@ require 'spec_helper'
4
4
 
5
5
  describe Granite::Form::Model::Attributes::Reflections::Represents::BuildTypeDefinition do
6
6
  def build_type_definition(name = :name, **options)
7
- @reflection = Granite::Form::Model::Attributes::Reflections::Represents.new(name, options.reverse_merge(of: :author))
7
+ @reflection = Granite::Form::Model::Attributes::Reflections::Represents.new(name,
8
+ options.reverse_merge(of: :author))
8
9
  described_class.new(owner, @reflection).call
9
10
  end
10
11
 
@@ -102,6 +103,7 @@ describe Granite::Form::Model::Attributes::Reflections::Represents::BuildTypeDef
102
103
  it { expect(build_type_definition(:status)).to have_type(Integer) }
103
104
  it { expect(build_type_definition(:full_name)).to have_type(String) }
104
105
  it { expect(build_type_definition(:unknown_attribute)).to have_type(Object) }
106
+
105
107
  it do
106
108
  attribute = build_type_definition(:related_ids)
107
109
  expect(attribute).to be_a(Granite::Form::Types::Collection)
@@ -23,7 +23,7 @@ describe Granite::Form::Model::Attributes::Reflections::Represents do
23
23
  end
24
24
 
25
25
  let(:instance) { Target.new attributes }
26
- let(:attributes) { {subject: Author.new} }
26
+ let(:attributes) { { subject: Author.new } }
27
27
  let!(:reflection) { build_reflection }
28
28
 
29
29
  it { expect(reflection.reference).to eq('author') }
@@ -53,7 +53,7 @@ describe Granite::Form::Model::Attributes::Reflections::Represents do
53
53
  context 'when validate_reference is false' do
54
54
  let(:reflection) { build_reflection(validate_reference: false) }
55
55
 
56
- it { expect { instance.validate }.not_to change { instance.errors.messages } }
56
+ it { expect { instance.validate }.not_to(change { instance.errors.messages }) }
57
57
  end
58
58
  end
59
59
  end
@@ -23,9 +23,9 @@ describe Granite::Form::Model::Attributes::Represents do
23
23
  describe '#initialize' do
24
24
  before { Author.attribute :name, String, default: 'Default Name' }
25
25
 
26
- let(:attributes) { {foo: 'bar'} }
26
+ let(:attributes) { { foo: 'bar' } }
27
27
 
28
- it { expect { Model.new(attributes) }.to_not change { attributes } }
28
+ it { expect { Model.new(attributes) }.not_to(change { attributes }) }
29
29
 
30
30
  it { expect(attribute.read).to eq('Default Name') }
31
31
  it { expect(attribute.read_before_type_cast).to eq('Default Name') }
@@ -37,28 +37,33 @@ describe Granite::Form::Model::Attributes do
37
37
  describe '.inspect' do
38
38
  specify { expect(stub_model.inspect).to match(/#<Class:0x\w+>\(no attributes\)/) }
39
39
  specify { expect(stub_model(:user).inspect).to eq('User(no attributes)') }
40
+
40
41
  specify do
41
42
  expect(stub_model do
42
43
  include Granite::Form::Model::Primary
43
44
  primary :count, Integer
44
45
  attribute :object, Object
45
- end.inspect).to match(/#<Class:0x\w+>\(\*count: Integer, object: Object\)/) end
46
+ end.inspect).to match(/#<Class:0x\w+>\(\*count: Integer, object: Object\)/)
47
+ end
48
+
46
49
  specify do
47
50
  expect(stub_model(:user) do
48
51
  include Granite::Form::Model::Primary
49
52
  primary :count, Integer
50
53
  attribute :object, Object
51
- end.inspect).to match('User(*count: Integer, object: Object)') end
54
+ end.inspect).to match('User(*count: Integer, object: Object)')
55
+ end
52
56
  end
53
57
 
54
58
  describe '#==' do
59
+ subject { model.new name: 'hello', count: 42 }
60
+
55
61
  let(:model) do
56
62
  stub_model do
57
63
  attribute :name, String
58
64
  attribute :count, Float, default: 0
59
65
  end
60
66
  end
61
- subject { model.new name: 'hello', count: 42 }
62
67
 
63
68
  it { is_expected.not_to eq(nil) }
64
69
  it { is_expected.not_to eq('hello') }
@@ -79,6 +84,7 @@ describe Granite::Form::Model::Attributes do
79
84
 
80
85
  describe '#attribute' do
81
86
  let(:instance) { model.new }
87
+
82
88
  specify { expect(instance.attribute(:full_name).reflection.name).to eq('full_name') }
83
89
  specify { expect(instance.attribute('full_name').reflection.name).to eq('full_name') }
84
90
  specify { expect(instance.attribute(:name).reflection.name).to eq('full_name') }
@@ -108,10 +114,12 @@ describe Granite::Form::Model::Attributes do
108
114
 
109
115
  describe '#attributes' do
110
116
  specify { expect(stub_model.new.attributes).to eq({}) }
117
+
111
118
  specify do
112
119
  expect(model.new(name: 'Name').attributes)
113
120
  .to match('id' => nil, 'full_name' => 'Name', 'author' => nil, 'projects' => nil)
114
121
  end
122
+
115
123
  specify do
116
124
  expect(model.new(name: 'Name').attributes(false))
117
125
  .to match('id' => nil, 'full_name' => 'Name')
@@ -119,13 +127,16 @@ describe Granite::Form::Model::Attributes do
119
127
  end
120
128
 
121
129
  describe '#assign_attributes' do
122
- let(:attributes) { {id: 42, full_name: 'Name', missed: 'value'} }
123
130
  subject { model.new }
124
131
 
132
+ let(:attributes) { { id: 42, full_name: 'Name', missed: 'value' } }
133
+
125
134
  specify { expect { subject.assign_attributes(attributes) }.to change { subject.id }.to(42) }
126
135
  specify { expect { subject.assign_attributes(attributes) }.to change { subject.full_name }.to('Name') }
127
136
 
128
137
  context 'features stack and assign order' do
138
+ subject { model.new }
139
+
129
140
  let(:model) do
130
141
  stub_model do
131
142
  attr_reader :logger
@@ -147,7 +158,6 @@ describe Granite::Form::Model::Attributes do
147
158
  log(:plain2)
148
159
  end
149
160
  end
150
- subject { model.new }
151
161
 
152
162
  specify do
153
163
  expect { subject.assign_attributes(plain1: 'value', plain2: 'value') }
@@ -176,18 +186,22 @@ describe Granite::Form::Model::Attributes do
176
186
  log(:assoc_plain)
177
187
 
178
188
  def assign_attributes(attrs)
179
- super attrs.merge(attrs.extract!('plain2'))
189
+ super(attrs.merge(attrs.extract!('plain2')))
180
190
  end
181
191
  end
182
192
  end
183
193
 
184
194
  specify do
185
- expect { subject.assign_attributes(assoc_plain: 'value', assoc_attributes: {}, plain1: 'value', plain2: 'value') }
195
+ expect do
196
+ subject.assign_attributes(assoc_plain: 'value', assoc_attributes: {}, plain1: 'value', plain2: 'value')
197
+ end
186
198
  .to change { subject.logger }.to(%i[plain1 assoc_attributes assoc_plain plain2])
187
199
  end
188
200
 
189
201
  specify do
190
- expect { subject.assign_attributes(plain1: 'value', plain2: 'value', assoc_plain: 'value', assoc_attributes: {}) }
202
+ expect do
203
+ subject.assign_attributes(plain1: 'value', plain2: 'value', assoc_plain: 'value', assoc_attributes: {})
204
+ end
191
205
  .to change { subject.logger }.to(%i[plain1 assoc_attributes assoc_plain plain2])
192
206
  end
193
207
  end
@@ -212,7 +226,7 @@ describe Granite::Form::Model::Attributes do
212
226
 
213
227
  let(:author) { Author.new }
214
228
  let(:model) { Model.new(attributes) }
215
- let(:attributes) { {author: author, name: 'Author Name', full_name: nil, age: 25} }
229
+ let(:attributes) { { author: author, name: 'Author Name', full_name: nil, age: 25 } }
216
230
 
217
231
  it { expect { model.sync_attributes }.to change(author, :name).to('Author Name') }
218
232
 
@@ -226,21 +240,27 @@ describe Granite::Form::Model::Attributes do
226
240
  describe '#inspect' do
227
241
  specify { expect(stub_model.new.inspect).to match(/#<#<Class:0x\w+> \(no attributes\)>/) }
228
242
  specify { expect(stub_model(:user).new.inspect).to match(/#<User \(no attributes\)>/) }
243
+
229
244
  specify do
230
245
  expect(stub_model do
231
246
  include Granite::Form::Model::Primary
232
247
  primary :count, Integer
233
248
  attribute :object, Object
234
- end.new(object: 'String').inspect).to match(/#<#<Class:0x\w+> \*count: nil, object: "String">/) end
249
+ end.new(object: 'String').inspect).to match(/#<#<Class:0x\w+> \*count: nil, object: "String">/)
250
+ end
251
+
235
252
  specify do
236
253
  expect(stub_model(:user) do
237
254
  include Granite::Form::Model::Primary
238
255
  primary :count, Integer
239
256
  attribute :object, Object
240
- end.new.inspect).to match(/#<User \*count: nil, object: nil>/) end
257
+ end.new.inspect).to match(/#<User \*count: nil, object: nil>/)
258
+ end
241
259
  end
242
260
 
243
261
  context 'attributes integration' do
262
+ subject { model.new('world') }
263
+
244
264
  let(:model) do
245
265
  stub_class do
246
266
  include Granite::Form::Util
@@ -257,9 +277,10 @@ describe Granite::Form::Model::Attributes do
257
277
  attribute :enum_with_default, Integer, enum: [1, 2, 3], default: '2'
258
278
  attribute :foo, Boolean, default: false
259
279
  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|
280
+ dictionary :dict, Integer, keys: %w[from to], enum: [1, 2, 3], default: { from: 1 }, normalizer: proc { |v|
261
281
  next v if v[:from].nil? || v[:to].nil? || v[:from] <= v[:to]
262
- {from: v[:to], to: v[:from]}.with_indifferent_access
282
+
283
+ { from: v[:to], to: v[:from] }.with_indifferent_access
263
284
  }
264
285
 
265
286
  def initialize(name = nil)
@@ -269,21 +290,19 @@ describe Granite::Form::Model::Attributes do
269
290
  end
270
291
  end
271
292
 
272
- subject { model.new('world') }
273
-
274
- its(:enum_values) { should == [1, 2, 3] }
275
- its(:string_default) { should == 'world' }
276
- its(:count_default) { should == '10' }
277
- its(:name) { should == 'world' }
278
- its(:hello) { should eq(nil) }
279
- its(:hello?) { should eq(false) }
280
- its(:count) { should == 10 }
281
- its(:count_before_type_cast) { should == '10' }
282
- its(:count_came_from_user?) { should eq(false) }
283
- its(:count?) { should eq(true) }
284
- its(:calc) { should == 5 }
285
- its(:enum?) { should eq(false) }
286
- its(:enum_with_default?) { should eq(true) }
293
+ its(:enum_values) { is_expected.to eq [1, 2, 3] }
294
+ its(:string_default) { is_expected.to eq 'world' }
295
+ its(:count_default) { is_expected.to eq '10' }
296
+ its(:name) { is_expected.to eq 'world' }
297
+ its(:hello) { is_expected.to eq(nil) }
298
+ its(:hello?) { is_expected.to eq(false) }
299
+ its(:count) { is_expected.to eq 10 }
300
+ its(:count_before_type_cast) { is_expected.to eq '10' }
301
+ its(:count_came_from_user?) { is_expected.to eq(false) }
302
+ its(:count?) { is_expected.to eq(true) }
303
+ its(:calc) { is_expected.to eq 5 }
304
+ its(:enum?) { is_expected.to eq(false) }
305
+ its(:enum_with_default?) { is_expected.to eq(true) }
287
306
  specify { expect { subject.hello = 'worlds' }.to change { subject.hello }.from(nil).to('worlds') }
288
307
  specify { expect { subject.count = 20 }.to change { subject.count }.from(10).to(20) }
289
308
  specify { expect { subject.calc = 15 }.to change { subject.calc }.from(5).to(15) }
@@ -294,22 +313,27 @@ describe Granite::Form::Model::Attributes do
294
313
  subject.enum = 3
295
314
  expect(subject.enum).to eq(3)
296
315
  end
316
+
297
317
  specify do
298
318
  subject.enum = '3'
299
319
  expect(subject.enum).to eq(3)
300
320
  end
321
+
301
322
  specify do
302
323
  subject.enum = 10
303
324
  expect(subject.enum).to eq(nil)
304
325
  end
326
+
305
327
  specify do
306
328
  subject.enum = 'hello'
307
329
  expect(subject.enum).to eq(nil)
308
330
  end
331
+
309
332
  specify do
310
333
  subject.enum_with_default = 3
311
334
  expect(subject.enum_with_default).to eq(3)
312
335
  end
336
+
313
337
  specify do
314
338
  subject.enum_with_default = 10
315
339
  expect(subject.enum_with_default).to be_nil
@@ -349,21 +373,37 @@ describe Granite::Form::Model::Attributes do
349
373
 
350
374
  specify do
351
375
  expect(subject).to have_attributes(
352
- dict: {from: 1},
353
- dict_before_type_cast: {from: 1},
376
+ dict: { from: 1 },
377
+ dict_before_type_cast: { from: 1 },
354
378
  dict?: true,
355
- dict_default: {from: 1},
379
+ dict_default: { from: 1 },
356
380
  dict_values: [1, 2, 3]
357
381
  )
358
382
  end
359
383
 
360
- specify { with_assigned_value(nil).to have_attributes(dict: {'from' => 1}, dict_before_type_cast: {from: 1}) }
384
+ specify { with_assigned_value(nil).to have_attributes(dict: { 'from' => 1 }, dict_before_type_cast: { from: 1 }) }
361
385
  specify { with_assigned_value([nil]).to have_attributes(dict: {}, dict_before_type_cast: [nil]) }
362
386
  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}) }
387
+
388
+ specify do
389
+ with_assigned_value(from: 1, to: 2)
390
+ .to have_attributes(dict: { 'from' => 1, 'to' => 2 }, dict_before_type_cast: { from: 1, to: 2 })
391
+ end
392
+
393
+ specify do
394
+ with_assigned_value(from: 2, to: 4)
395
+ .to have_attributes(dict: { 'from' => 2, 'to' => nil }, dict_before_type_cast: { from: 2, to: 4 })
396
+ end
397
+
398
+ specify do
399
+ with_assigned_value(from: '1', to: '2')
400
+ .to have_attributes(dict: { 'from' => 1, 'to' => 2 }, dict_before_type_cast: { from: '1', to: '2' })
401
+ end
402
+
403
+ specify do
404
+ with_assigned_value(from: 3, to: 1)
405
+ .to have_attributes(dict: { 'from' => 1, 'to' => 3 }, dict_before_type_cast: { from: 3, to: 1 })
406
+ end
367
407
  end
368
408
 
369
409
  context 'attribute caching' do
@@ -45,16 +45,19 @@ describe Granite::Form::Model::Dirty do
45
45
  .tap { |m| m.update(author_id: author.id) }.changes)
46
46
  .to eq('author_id' => [other_author.id, author.id])
47
47
  end
48
+
48
49
  specify do
49
50
  expect(Model.instantiate(author_id: other_author.id)
50
51
  .tap { |m| m.update(author: author) }.changes)
51
52
  .to eq('author_id' => [other_author.id, author.id])
52
53
  end
54
+
53
55
  specify do
54
56
  expect(Model.instantiate(author_ids: [other_author.id])
55
57
  .tap { |m| m.update(author_ids: [author.id]) }.changes)
56
58
  .to eq('author_ids' => [[other_author.id], [author.id]])
57
59
  end
60
+
58
61
  specify do
59
62
  expect(Model.instantiate(author_ids: [other_author.id])
60
63
  .tap { |m| m.update(authors: [author]) }.changes)
@@ -19,7 +19,7 @@ describe Granite::Form::Model::Persistence do
19
19
  context do
20
20
  subject(:instance) { model.instantiate(name: 'Hello', foo: 'Bar') }
21
21
 
22
- specify { expect(subject.instance_variable_get(:@initial_attributes)).to eq({name: 'Hello'}.stringify_keys) }
22
+ specify { expect(subject.instance_variable_get(:@initial_attributes)).to eq({ name: 'Hello' }.stringify_keys) }
23
23
  end
24
24
  end
25
25
 
@@ -28,17 +28,27 @@ describe Granite::Form::Model::Persistence do
28
28
  subject(:instances) { model.instantiate_collection(name: 'Hello', foo: 'Bar') }
29
29
 
30
30
  specify { expect(subject).to be_a Array }
31
- specify { expect(subject.first.instance_variable_get(:@initial_attributes)).to eq({name: 'Hello'}.stringify_keys) }
31
+
32
+ specify do
33
+ expect(subject.first.instance_variable_get(:@initial_attributes)).to eq({ name: 'Hello' }.stringify_keys)
34
+ end
32
35
  end
33
36
 
34
37
  context do
38
+ subject(:instances) { model.instantiate_collection([{ name: 'Hello', foo: 'Bar' }, { name: 'World' }]) }
39
+
35
40
  before { model.send(:include, Granite::Form::Model::Scopes) }
36
- subject(:instances) { model.instantiate_collection([{name: 'Hello', foo: 'Bar'}, {name: 'World'}]) }
37
41
 
38
42
  specify { expect(subject).to be_a Granite::Form::Model::Scopes::ScopeProxy }
39
43
  specify { expect(subject.count).to eq(2) }
40
- specify { expect(subject.first.instance_variable_get(:@initial_attributes)).to eq({name: 'Hello'}.stringify_keys) }
41
- specify { expect(subject.second.instance_variable_get(:@initial_attributes)).to eq({name: 'World'}.stringify_keys) }
44
+
45
+ specify do
46
+ expect(subject.first.instance_variable_get(:@initial_attributes)).to eq({ name: 'Hello' }.stringify_keys)
47
+ end
48
+
49
+ specify do
50
+ expect(subject.second.instance_variable_get(:@initial_attributes)).to eq({ name: 'World' }.stringify_keys)
51
+ end
42
52
  end
43
53
  end
44
54
  end