granite-form 0.1.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (133) hide show
  1. checksums.yaml +4 -4
  2. data/.github/CODEOWNERS +1 -2
  3. data/.github/workflows/{ci.yml → ruby.yml} +22 -4
  4. data/.rubocop.yml +1 -1
  5. data/.rubocop_todo.yml +3 -3
  6. data/Appraisals +1 -2
  7. data/CHANGELOG.md +14 -0
  8. data/README.md +0 -2
  9. data/Rakefile +4 -0
  10. data/docker-compose.yml +14 -0
  11. data/gemfiles/rails.5.0.gemfile +0 -1
  12. data/gemfiles/rails.5.1.gemfile +0 -1
  13. data/gemfiles/rails.5.2.gemfile +0 -1
  14. data/granite-form.gemspec +16 -15
  15. data/lib/granite/form/active_record/associations.rb +1 -1
  16. data/lib/granite/form/base.rb +1 -2
  17. data/lib/granite/form/config.rb +10 -10
  18. data/lib/granite/form/errors.rb +0 -15
  19. data/lib/granite/form/model/associations/base.rb +0 -4
  20. data/lib/granite/form/model/associations/collection/embedded.rb +2 -1
  21. data/lib/granite/form/model/associations/collection/proxy.rb +1 -1
  22. data/lib/granite/form/model/associations/embeds_any.rb +7 -0
  23. data/lib/granite/form/model/associations/embeds_many.rb +9 -58
  24. data/lib/granite/form/model/associations/embeds_one.rb +7 -36
  25. data/lib/granite/form/model/associations/nested_attributes.rb +8 -8
  26. data/lib/granite/form/model/associations/persistence_adapters/active_record.rb +0 -4
  27. data/lib/granite/form/model/associations/persistence_adapters/base.rb +0 -4
  28. data/lib/granite/form/model/associations/references_many.rb +0 -32
  29. data/lib/granite/form/model/associations/references_one.rb +0 -28
  30. data/lib/granite/form/model/associations/reflections/embeds_any.rb +1 -1
  31. data/lib/granite/form/model/associations/reflections/references_any.rb +0 -4
  32. data/lib/granite/form/model/associations/reflections/references_many.rb +3 -1
  33. data/lib/granite/form/model/associations/reflections/references_one.rb +3 -3
  34. data/lib/granite/form/model/associations/reflections/singular.rb +0 -8
  35. data/lib/granite/form/model/associations.rb +0 -6
  36. data/lib/granite/form/model/attributes/attribute.rb +1 -1
  37. data/lib/granite/form/model/attributes/base.rb +14 -17
  38. data/lib/granite/form/model/attributes/collection.rb +1 -1
  39. data/lib/granite/form/model/attributes/dictionary.rb +1 -1
  40. data/lib/granite/form/model/attributes/localized.rb +1 -1
  41. data/lib/granite/form/model/attributes/reference_many.rb +1 -1
  42. data/lib/granite/form/model/attributes/reference_one.rb +1 -9
  43. data/lib/granite/form/model/attributes/reflections/attribute.rb +0 -6
  44. data/lib/granite/form/model/attributes/reflections/base.rb +9 -12
  45. data/lib/granite/form/model/attributes/reflections/reference_one.rb +0 -10
  46. data/lib/granite/form/model/persistence.rb +1 -19
  47. data/lib/granite/form/model/validations/nested.rb +1 -1
  48. data/lib/granite/form/model.rb +0 -2
  49. data/lib/granite/form/types/active_support/time_zone.rb +22 -0
  50. data/lib/granite/form/types/array.rb +17 -0
  51. data/lib/granite/form/types/big_decimal.rb +15 -0
  52. data/lib/granite/form/types/boolean.rb +38 -0
  53. data/lib/granite/form/types/date.rb +15 -0
  54. data/lib/granite/form/types/date_time.rb +15 -0
  55. data/lib/granite/form/types/float.rb +15 -0
  56. data/lib/granite/form/types/hash_with_action_controller_parameters.rb +18 -0
  57. data/lib/granite/form/types/integer.rb +13 -0
  58. data/lib/granite/form/types/object.rb +30 -0
  59. data/lib/granite/form/types/string.rb +13 -0
  60. data/lib/granite/form/types/time.rb +15 -0
  61. data/lib/granite/form/types/uuid.rb +22 -0
  62. data/lib/granite/form/types.rb +15 -0
  63. data/lib/granite/form/version.rb +1 -1
  64. data/lib/granite/form.rb +19 -118
  65. data/spec/{lib/granite → granite}/form/active_record/associations_spec.rb +16 -18
  66. data/spec/{lib/granite → granite}/form/active_record/nested_attributes_spec.rb +0 -1
  67. data/spec/{lib/granite → granite}/form/config_spec.rb +22 -10
  68. data/spec/granite/form/extensions_spec.rb +12 -0
  69. data/spec/{lib/granite → granite}/form/model/associations/embeds_many_spec.rb +29 -305
  70. data/spec/{lib/granite → granite}/form/model/associations/embeds_one_spec.rb +27 -212
  71. data/spec/granite/form/model/associations/nested_attributes_spec.rb +23 -0
  72. data/spec/{lib/granite → granite}/form/model/associations/persistence_adapters/active_record_spec.rb +0 -0
  73. data/spec/granite/form/model/associations/references_many_spec.rb +251 -0
  74. data/spec/granite/form/model/associations/references_one_spec.rb +173 -0
  75. data/spec/{lib/granite → granite}/form/model/associations/reflections/embeds_any_spec.rb +1 -2
  76. data/spec/{lib/granite → granite}/form/model/associations/reflections/embeds_many_spec.rb +18 -26
  77. data/spec/{lib/granite → granite}/form/model/associations/reflections/embeds_one_spec.rb +16 -23
  78. data/spec/{lib/granite → granite}/form/model/associations/reflections/references_many_spec.rb +1 -1
  79. data/spec/{lib/granite → granite}/form/model/associations/reflections/references_one_spec.rb +1 -22
  80. data/spec/{lib/granite → granite}/form/model/associations/validations_spec.rb +0 -3
  81. data/spec/{lib/granite → granite}/form/model/associations_spec.rb +3 -24
  82. data/spec/{lib/granite → granite}/form/model/attributes/attribute_spec.rb +4 -46
  83. data/spec/{lib/granite → granite}/form/model/attributes/base_spec.rb +11 -2
  84. data/spec/{lib/granite → granite}/form/model/attributes/collection_spec.rb +0 -0
  85. data/spec/{lib/granite → granite}/form/model/attributes/dictionary_spec.rb +0 -0
  86. data/spec/{lib/granite → granite}/form/model/attributes/localized_spec.rb +1 -1
  87. data/spec/{lib/granite → granite}/form/model/attributes/reflections/attribute_spec.rb +0 -12
  88. data/spec/{lib/granite → granite}/form/model/attributes/reflections/base_spec.rb +1 -1
  89. data/spec/{lib/granite → granite}/form/model/attributes/reflections/collection_spec.rb +0 -0
  90. data/spec/{lib/granite → granite}/form/model/attributes/reflections/dictionary_spec.rb +0 -0
  91. data/spec/{lib/granite → granite}/form/model/attributes/reflections/localized_spec.rb +0 -0
  92. data/spec/{lib/granite → granite}/form/model/attributes/reflections/represents_spec.rb +0 -0
  93. data/spec/{lib/granite → granite}/form/model/attributes/represents_spec.rb +0 -0
  94. data/spec/{lib/granite → granite}/form/model/attributes_spec.rb +0 -0
  95. data/spec/{lib/granite → granite}/form/model/conventions_spec.rb +0 -0
  96. data/spec/{lib/granite → granite}/form/model/dirty_spec.rb +1 -1
  97. data/spec/{lib/granite → granite}/form/model/persistence_spec.rb +0 -2
  98. data/spec/{lib/granite → granite}/form/model/primary_spec.rb +1 -1
  99. data/spec/{lib/granite → granite}/form/model/representation_spec.rb +0 -0
  100. data/spec/{lib/granite → granite}/form/model/scopes_spec.rb +0 -0
  101. data/spec/{lib/granite → granite}/form/model/validations/associated_spec.rb +2 -4
  102. data/spec/{lib/granite → granite}/form/model/validations/nested_spec.rb +57 -15
  103. data/spec/{lib/granite → granite}/form/model/validations_spec.rb +0 -0
  104. data/spec/{lib/granite → granite}/form/model_spec.rb +0 -0
  105. data/spec/granite/form/types/active_support/time_zone_spec.rb +24 -0
  106. data/spec/granite/form/types/array_spec.rb +13 -0
  107. data/spec/granite/form/types/big_decimal_spec.rb +19 -0
  108. data/spec/granite/form/types/boolean_spec.rb +21 -0
  109. data/spec/granite/form/types/date_spec.rb +18 -0
  110. data/spec/granite/form/types/date_time_spec.rb +20 -0
  111. data/spec/granite/form/types/float_spec.rb +19 -0
  112. data/spec/granite/form/types/hash_with_action_controller_parameters_spec.rb +22 -0
  113. data/spec/granite/form/types/integer_spec.rb +18 -0
  114. data/spec/granite/form/types/object_spec.rb +40 -0
  115. data/spec/granite/form/types/string_spec.rb +13 -0
  116. data/spec/granite/form/types/time_spec.rb +31 -0
  117. data/spec/granite/form/types/uuid_spec.rb +21 -0
  118. data/spec/{lib/granite → granite}/form_spec.rb +0 -0
  119. data/spec/spec_helper.rb +0 -15
  120. data/spec/support/active_record.rb +20 -0
  121. data/spec/{shared → support/shared}/nested_attribute_examples.rb +3 -21
  122. data/spec/support/shared/type_examples.rb +7 -0
  123. metadata +173 -123
  124. data/.github/workflows/main.yml +0 -29
  125. data/gemfiles/rails.4.2.gemfile +0 -15
  126. data/lib/granite/form/model/callbacks.rb +0 -72
  127. data/lib/granite/form/model/lifecycle.rb +0 -309
  128. data/spec/lib/granite/form/model/associations/nested_attributes_spec.rb +0 -119
  129. data/spec/lib/granite/form/model/associations/references_many_spec.rb +0 -572
  130. data/spec/lib/granite/form/model/associations/references_one_spec.rb +0 -445
  131. data/spec/lib/granite/form/model/callbacks_spec.rb +0 -337
  132. data/spec/lib/granite/form/model/lifecycle_spec.rb +0 -356
  133. data/spec/lib/granite/form/model/typecasting_spec.rb +0 -193
@@ -0,0 +1,173 @@
1
+ require 'spec_helper'
2
+
3
+ describe Granite::Form::Model::Associations::ReferencesOne do
4
+ before do
5
+ stub_class(:author, ActiveRecord::Base) do
6
+ validates :name, presence: true
7
+ end
8
+
9
+ stub_model(:book) do
10
+ include Granite::Form::Model::Persistence
11
+ include Granite::Form::Model::Associations
12
+
13
+ attribute :title, String
14
+ references_one :author
15
+ end
16
+ end
17
+
18
+ let(:author) { Author.create!(name: 'Johny') }
19
+ let(:other) { Author.create!(name: 'Other') }
20
+ let(:book) { Book.new }
21
+ let(:association) { book.association(:author) }
22
+
23
+ let(:existing_book) { Book.instantiate title: 'My Life', author_id: author.id }
24
+ let(:existing_association) { existing_book.association(:author) }
25
+
26
+ describe 'book#association' do
27
+ specify { expect(association).to be_a described_class }
28
+ specify { expect(association).to eq(book.association(:author)) }
29
+ end
30
+
31
+ describe 'book#inspect' do
32
+ specify { expect(existing_book.inspect).to eq(<<~STR.chomp) }
33
+ #<Book author: #<ReferencesOne #{author.inspect}>, title: "My Life", author_id: #{author.id}>
34
+ STR
35
+ end
36
+
37
+ describe '#target' do
38
+ specify { expect(association.target).to be_nil }
39
+ specify { expect(existing_association.target).to eq(existing_book.author) }
40
+ end
41
+
42
+ describe '#loaded?' do
43
+ let(:new_author) { Author.create(name: 'Morty') }
44
+
45
+ specify { expect(association.loaded?).to eq(false) }
46
+ specify { expect { association.target }.to change { association.loaded? }.to(true) }
47
+ specify { expect { association.replace(new_author) }.to change { association.loaded? }.to(true) }
48
+ specify { expect { association.replace(nil) }.to change { association.loaded? }.to(true) }
49
+ specify { expect { existing_association.replace(new_author) }.to change { existing_association.loaded? }.to(true) }
50
+ specify { expect { existing_association.replace(nil) }.to change { existing_association.loaded? }.to(true) }
51
+ end
52
+
53
+ describe '#reload' do
54
+ specify { expect(association.reload).to be_nil }
55
+
56
+ specify { expect(existing_association.reload).to be_a Author }
57
+ specify { expect(existing_association.reload).to be_persisted }
58
+
59
+ context do
60
+ before { existing_association.reader.name = 'New' }
61
+ specify do
62
+ expect { existing_association.reload }
63
+ .to change { existing_association.reader.name }
64
+ .from('New').to('Johny')
65
+ end
66
+ end
67
+ end
68
+
69
+ describe '#reader' do
70
+ specify { expect(association.reader).to be_nil }
71
+
72
+ specify { expect(existing_association.reader).to be_a Author }
73
+ specify { expect(existing_association.reader).to be_persisted }
74
+ end
75
+
76
+ describe '#default' do
77
+ before { Book.references_one :author, default: ->(_book) { author.id } }
78
+ let(:existing_book) { Book.instantiate title: 'My Life' }
79
+
80
+ specify { expect(association.target).to eq(author) }
81
+ specify { expect { association.replace(other) }.to change { association.target }.to(other) }
82
+ specify { expect { association.replace(nil) }.to change { association.target }.to be_nil }
83
+
84
+ specify { expect(existing_association.target).to be_nil }
85
+ specify { expect { existing_association.replace(other) }.to change { existing_association.target }.to(other) }
86
+ specify { expect { existing_association.replace(nil) }.not_to change { existing_association.target } }
87
+ end
88
+
89
+ describe '#writer' do
90
+ context 'new owner' do
91
+ let(:new_author) { Author.new(name: 'Morty') }
92
+
93
+ let(:book) do
94
+ Book.new.tap do |book|
95
+ book.send(:mark_persisted!)
96
+ end
97
+ end
98
+
99
+ specify do
100
+ expect { association.writer(nil) }
101
+ .not_to change { book.author_id }
102
+ end
103
+ specify do
104
+ expect { association.writer(new_author) }
105
+ .to change { muffle(NoMethodError) { association.reader.name } }
106
+ .from(nil).to('Morty')
107
+ end
108
+ specify do
109
+ expect { association.writer(new_author) }
110
+ .not_to change { book.author_id }.from(nil)
111
+ end
112
+ end
113
+
114
+ context 'persisted owner' do
115
+ let(:new_author) { Author.create!(name: 'Morty') }
116
+
117
+ specify do
118
+ expect { association.writer(stub_model(:dummy).new) }
119
+ .to raise_error Granite::Form::AssociationTypeMismatch
120
+ end
121
+
122
+ specify { expect(association.writer(nil)).to be_nil }
123
+ specify { expect(association.writer(new_author)).to eq(new_author) }
124
+ specify do
125
+ expect { association.writer(nil) }
126
+ .not_to change { book.read_attribute(:author_id) }
127
+ end
128
+ specify do
129
+ expect { association.writer(new_author) }
130
+ .to change { association.reader.try(:attributes) }.from(nil).to('id' => new_author.id, 'name' => 'Morty')
131
+ end
132
+ specify do
133
+ expect { association.writer(new_author) }
134
+ .to change { book.read_attribute(:author_id) }
135
+ end
136
+
137
+ context do
138
+ before do
139
+ stub_class(:dummy, ActiveRecord::Base) do
140
+ self.table_name = :authors
141
+ end
142
+ end
143
+
144
+ specify do
145
+ expect { muffle(Granite::Form::AssociationTypeMismatch) { existing_association.writer(Dummy.new) } }
146
+ .not_to change { existing_book.read_attribute(:author_id) }
147
+ end
148
+ specify do
149
+ expect { muffle(Granite::Form::AssociationTypeMismatch) { existing_association.writer(Dummy.new) } }
150
+ .not_to change { existing_association.reader }
151
+ end
152
+ end
153
+
154
+ specify { expect(existing_association.writer(nil)).to be_nil }
155
+ specify { expect(existing_association.writer(new_author)).to eq(new_author) }
156
+ specify do
157
+ expect { existing_association.writer(nil) }
158
+ .to change { existing_book.read_attribute(:author_id) }
159
+ .from(author.id).to(nil)
160
+ end
161
+ specify do
162
+ expect { existing_association.writer(new_author) }
163
+ .to change { existing_association.reader.try(:attributes) }
164
+ .from('id' => author.id, 'name' => 'Johny').to('id' => new_author.id, 'name' => 'Morty')
165
+ end
166
+ specify do
167
+ expect { existing_association.writer(new_author) }
168
+ .to change { existing_book.read_attribute(:author_id) }
169
+ .from(author.id).to(new_author.id)
170
+ end
171
+ end
172
+ end
173
+ end
@@ -7,7 +7,6 @@ describe Granite::Form::Model::Associations::Reflections::EmbedsAny do
7
7
 
8
8
  before do
9
9
  stub_model(:project) do
10
- include Granite::Form::Model::Lifecycle
11
10
  attribute :title, String
12
11
  end
13
12
  stub_model(:user) do
@@ -20,7 +19,7 @@ describe Granite::Form::Model::Associations::Reflections::EmbedsAny do
20
19
 
21
20
  it { is_expected.to be_a(Granite::Form::Model) }
22
21
  it { is_expected.to be_a(Granite::Form::Model::Primary) }
23
- it { is_expected.to be_a(Granite::Form::Model::Lifecycle) }
22
+ it { is_expected.to be_a(Granite::Form::Model::Persistence) }
24
23
  it { is_expected.to be_a(Granite::Form::Model::Associations) }
25
24
 
26
25
  context 'when Granite::Form.base_concern is defined' do
@@ -3,7 +3,8 @@ require 'spec_helper'
3
3
  describe Granite::Form::Model::Associations::Reflections::EmbedsMany do
4
4
  before do
5
5
  stub_model(:project) do
6
- include Granite::Form::Model::Lifecycle
6
+ include Granite::Form::Model::Persistence
7
+ include Granite::Form::Model::Associations
7
8
  attribute :title, String
8
9
  end
9
10
  stub_model(:user) do
@@ -24,24 +25,28 @@ describe Granite::Form::Model::Associations::Reflections::EmbedsMany do
24
25
  attribute :name
25
26
  embeds_many :projects,
26
27
  read: lambda { |reflection, object|
27
- value = object.read_attribute(reflection.name)
28
+ value = object.instance_variable_get("@_value_#{reflection.name}")
28
29
  JSON.parse(value) if value.present?
29
30
  },
30
31
  write: lambda { |reflection, object, value|
31
- object.write_attribute(reflection.name, value.to_json)
32
+ value = value.to_json if value
33
+ object.instance_variable_set("@_value_#{reflection.name}", value)
32
34
  }
33
35
  end
34
36
  end
35
37
 
36
- let(:user) { User.instantiate name: 'Rick', projects: [{title: 'Genesis'}].to_json }
38
+ let(:user) { User.new }
37
39
  let(:new_project1) { Project.new(title: 'Project 1') }
38
40
  let(:new_project2) { Project.new(title: 'Project 2') }
39
41
 
40
42
  specify do
41
- expect { user.projects.concat([new_project1, new_project2]) }
42
- .to change { user.read_attribute(:projects) }
43
- .from([{title: 'Genesis'}].to_json)
44
- .to([{title: 'Genesis'}, {title: 'Project 1'}, {title: 'Project 2'}].to_json)
43
+ expect do
44
+ user.projects = [new_project1, new_project2]
45
+ user.association(:projects).sync
46
+ end
47
+ .to change { user.projects(true) }
48
+ .from([])
49
+ .to([have_attributes(title: 'Project 1'), have_attributes(title: 'Project 2')])
45
50
  end
46
51
  end
47
52
 
@@ -54,23 +59,10 @@ describe Granite::Form::Model::Associations::Reflections::EmbedsMany do
54
59
  specify { expect { user.projects.build(title: 'Project') }.to change { user.projects }.from([]).to([project]) }
55
60
  end
56
61
 
57
- describe '#create' do
58
- let(:project) { Project.new title: 'Project' }
59
- specify { expect(user.projects.create(title: 'Project')).to eq(project) }
60
- specify { expect { user.projects.create(title: 'Project') }.to change { user.projects }.from([]).to([project]) }
61
- end
62
-
63
- describe '#create!' do
64
- let(:project) { Project.new title: 'Project' }
65
- specify { expect(user.projects.create!(title: 'Project')).to eq(project) }
66
- specify { expect { user.projects.create!(title: 'Project') }.to change { user.projects }.from([]).to([project]) }
67
- end
68
-
69
62
  describe '#reload' do
70
63
  let(:project) { Project.new title: 'Project' }
71
64
  before do
72
- user.update(projects: [project])
73
- user.apply_association_changes!
65
+ user.write_attribute(:projects, [{title: 'Project'}])
74
66
  end
75
67
  before { user.projects.build }
76
68
 
@@ -120,8 +112,8 @@ describe Granite::Form::Model::Associations::Reflections::EmbedsMany do
120
112
 
121
113
  specify { expect(User.reflect_on_association(:projects).klass).to eq(User::Project) }
122
114
  specify { expect(User.new.projects).to eq([]) }
123
- specify { expect(User.new.tap { |u| u.projects.create(title: 'Project') }.projects).to be_a(Granite::Form::Model::Associations::Collection::Embedded) }
124
- specify { expect(User.new.tap { |u| u.projects.create(title: 'Project') }.read_attribute(:projects)).to eq([{'title' => 'Project'}]) }
115
+ specify { expect(User.new.tap { |u| u.projects.build(title: 'Project') }.projects).to be_a(Granite::Form::Model::Associations::Collection::Embedded) }
116
+ specify { expect(User.new.tap { |u| u.projects.build(title: 'Project') }.projects).to match([have_attributes(title: 'Project')]) }
125
117
  end
126
118
 
127
119
  context do
@@ -138,8 +130,8 @@ describe Granite::Form::Model::Associations::Reflections::EmbedsMany do
138
130
 
139
131
  specify { expect(User.reflect_on_association(:projects).klass).to eq(User::Project) }
140
132
  specify { expect(User.new.projects).to eq([]) }
141
- specify { expect(User.new.tap { |u| u.projects.create(title: 'Project') }.projects).to be_a(Granite::Form::Model::Associations::Collection::Embedded) }
142
- specify { expect(User.new.tap { |u| u.projects.create(title: 'Project') }.read_attribute(:projects)).to eq([{'title' => 'Project', 'value' => nil}]) }
133
+ specify { expect(User.new.tap { |u| u.projects.build(title: 'Project') }.projects).to be_a(Granite::Form::Model::Associations::Collection::Embedded) }
134
+ specify { expect(User.new.tap { |u| u.projects.build(title: 'Project') }.projects).to match([have_attributes(title: 'Project', value: nil)]) }
143
135
  end
144
136
  end
145
137
  end
@@ -3,7 +3,8 @@ require 'spec_helper'
3
3
  describe Granite::Form::Model::Associations::Reflections::EmbedsOne do
4
4
  before do
5
5
  stub_model(:author) do
6
- include Granite::Form::Model::Lifecycle
6
+ include Granite::Form::Model::Persistence
7
+ include Granite::Form::Model::Associations
7
8
  attribute :name, String
8
9
  end
9
10
 
@@ -27,22 +28,26 @@ describe Granite::Form::Model::Associations::Reflections::EmbedsOne do
27
28
  attribute :title
28
29
  embeds_one :author,
29
30
  read: lambda { |reflection, object|
30
- value = object.read_attribute(reflection.name)
31
+ value = object.instance_variable_get("@_value_#{reflection.name}")
31
32
  JSON.parse(value) if value.present?
32
33
  },
33
34
  write: lambda { |reflection, object, value|
34
- object.write_attribute(reflection.name, value ? value.to_json : nil)
35
+ value = value.to_json if value
36
+ object.instance_variable_set("@_value_#{reflection.name}", value)
35
37
  }
36
38
  end
37
39
  end
38
40
 
39
- let(:book) { Book.instantiate author: {name: 'Duke'}.to_json }
41
+ let(:book) { Book.new }
40
42
  let(:author) { Author.new(name: 'Rick') }
41
43
 
42
44
  specify do
43
- expect { book.author = author }
44
- .to change { book.read_attribute(:author) }
45
- .from({name: 'Duke'}.to_json).to({name: 'Rick'}.to_json)
45
+ expect do
46
+ book.author = author
47
+ book.association(:author).sync
48
+ end
49
+ .to change { book.author(true) }
50
+ .from(nil).to(have_attributes(name: 'Rick'))
46
51
  end
47
52
  end
48
53
 
@@ -65,18 +70,6 @@ describe Granite::Form::Model::Associations::Reflections::EmbedsOne do
65
70
  specify { expect { book.build_author(name: 'Author') }.to change { book.author }.from(nil).to(author) }
66
71
  end
67
72
 
68
- describe '#create_author' do
69
- let(:author) { Author.new name: 'Author' }
70
- specify { expect(book.create_author(name: 'Author')).to eq(author) }
71
- specify { expect { book.create_author(name: 'Author') }.to change { book.author }.from(nil).to(author) }
72
- end
73
-
74
- describe '#create_author!' do
75
- let(:author) { Author.new name: 'Author' }
76
- specify { expect(book.create_author!(name: 'Author')).to eq(author) }
77
- specify { expect { book.create_author!(name: 'Author') }.to change { book.author }.from(nil).to(author) }
78
- end
79
-
80
73
  context 'on the fly' do
81
74
  context do
82
75
  before do
@@ -92,8 +85,8 @@ describe Granite::Form::Model::Associations::Reflections::EmbedsOne do
92
85
 
93
86
  specify { expect(Book.reflect_on_association(:author).klass).to eq(Book::Author) }
94
87
  specify { expect(Book.new.author).to be_nil }
95
- specify { expect(Book.new.tap { |b| b.create_author(name: 'Author') }.author).to be_a(Book::Author) }
96
- specify { expect(Book.new.tap { |b| b.create_author(name: 'Author') }.read_attribute(:author)).to eq('name' => 'Author') }
88
+ specify { expect(Book.new.tap { |b| b.build_author(name: 'Author') }.author).to be_a(Book::Author) }
89
+ specify { expect(Book.new.tap { |b| b.build_author(name: 'Author') }.author).to have_attributes(name: 'Author') }
97
90
  end
98
91
 
99
92
  context do
@@ -110,8 +103,8 @@ describe Granite::Form::Model::Associations::Reflections::EmbedsOne do
110
103
 
111
104
  specify { expect(Book.reflect_on_association(:author).klass).to eq(Book::Author) }
112
105
  specify { expect(Book.new.author).to be_nil }
113
- specify { expect(Book.new.tap { |b| b.create_author(name: 'Author') }.author).to be_a(Book::Author) }
114
- specify { expect(Book.new.tap { |b| b.create_author(name: 'Author') }.read_attribute(:author)).to eq('name' => 'Author', 'age' => nil) }
106
+ specify { expect(Book.new.tap { |b| b.build_author(name: 'Author') }.author).to be_a(Book::Author) }
107
+ specify { expect(Book.new.tap { |b| b.build_author(name: 'Author') }.author).to have_attributes(name: 'Author', age: nil) }
115
108
  end
116
109
  end
117
110
  end
@@ -4,7 +4,7 @@ describe Granite::Form::Model::Associations::Reflections::ReferencesMany do
4
4
  before do
5
5
  stub_class(:author, ActiveRecord::Base) do
6
6
  scope :name_starts_with_a, -> { name_starts_with('a') }
7
- scope :name_starts_with, ->(letter) { where("name LIKE '#{letter}%'") }
7
+ scope :name_starts_with, ->(letter) { where("name ILIKE '#{letter}%'") }
8
8
  end
9
9
 
10
10
  stub_model(:book) do
@@ -4,7 +4,7 @@ describe Granite::Form::Model::Associations::Reflections::ReferencesOne do
4
4
  before do
5
5
  stub_class(:author, ActiveRecord::Base) do
6
6
  scope :name_starts_with_a, -> { name_starts_with('a') }
7
- scope :name_starts_with, ->(letter) { where("name LIKE '#{letter}%'") }
7
+ scope :name_starts_with, ->(letter) { where("name ILIKE '#{letter}%'") }
8
8
  end
9
9
 
10
10
  stub_model(:book) do
@@ -263,25 +263,4 @@ describe Granite::Form::Model::Associations::Reflections::ReferencesOne do
263
263
  specify { expect { book.author_id = nil }.to change { book.author }.from(other).to(nil) }
264
264
  end
265
265
  end
266
-
267
- describe '#build_author' do
268
- specify { expect(book.build_author(name: 'Author')).to be_a(Author) }
269
- specify { expect(book.build_author(name: 'Author')).not_to be_persisted }
270
- specify { expect { book.build_author(name: 'Author') }.to change { book.author }.from(nil).to(an_instance_of(Author)) }
271
- specify { expect { book.build_author(name: 'Author') }.not_to change { book.author_id }.from(nil) }
272
- end
273
-
274
- describe '#create_author' do
275
- specify { expect(book.create_author(name: 'Author')).to be_a(Author) }
276
- specify { expect(book.create_author(name: 'Author')).to be_persisted }
277
- specify { expect { book.create_author(name: 'Author') }.to change { book.author }.from(nil).to(an_instance_of(Author)) }
278
- specify { expect { book.create_author(name: 'Author') }.to change { book.author_id }.from(nil).to(be_a(Integer)) }
279
- end
280
-
281
- describe '#create_author!' do
282
- specify { expect(book.create_author!(name: 'Author')).to be_a(Author) }
283
- specify { expect(book.create_author!(name: 'Author')).to be_persisted }
284
- specify { expect { book.create_author!(name: 'Author') }.to change { book.author }.from(nil).to(an_instance_of(Author)) }
285
- specify { expect { book.create_author!(name: 'Author') }.to change { book.author_id }.from(nil).to(be_a(Integer)) }
286
- end
287
266
  end
@@ -3,7 +3,6 @@ require 'spec_helper'
3
3
  describe Granite::Form::Model::Associations::Validations do
4
4
  before do
5
5
  stub_model(:project) do
6
- include Granite::Form::Model::Lifecycle
7
6
  include Granite::Form::Model::Associations
8
7
  include Granite::Form::Model::Associations::Validations
9
8
 
@@ -17,8 +16,6 @@ describe Granite::Form::Model::Associations::Validations do
17
16
  end
18
17
 
19
18
  stub_model(:profile) do
20
- include Granite::Form::Model::Lifecycle
21
-
22
19
  attribute :first_name, String
23
20
  attribute :last_name, String
24
21
  validates :first_name, presence: true
@@ -7,7 +7,7 @@ describe Granite::Form::Model::Associations do
7
7
  include Granite::Form::Model::Associations
8
8
  end
9
9
  stub_model(:project) do
10
- include Granite::Form::Model::Lifecycle
10
+ include Granite::Form::Model::Persistence
11
11
  end
12
12
  stub_model(:user, Nobody) do
13
13
  include Granite::Form::Model::Associations
@@ -65,7 +65,7 @@ describe Granite::Form::Model::Associations do
65
65
  context do
66
66
  before do
67
67
  stub_model(:project) do
68
- include Granite::Form::Model::Lifecycle
68
+ include Granite::Form::Model::Persistence
69
69
  include Granite::Form::Model::Associations
70
70
 
71
71
  attribute :title, String
@@ -80,7 +80,7 @@ describe Granite::Form::Model::Associations do
80
80
  end
81
81
 
82
82
  stub_model(:profile) do
83
- include Granite::Form::Model::Lifecycle
83
+ include Granite::Form::Model::Persistence
84
84
 
85
85
  attribute :first_name, String
86
86
  attribute :last_name, String
@@ -160,27 +160,6 @@ describe Granite::Form::Model::Associations do
160
160
  specify { expect(user.association_names).to eq(%i[profile projects]) }
161
161
  end
162
162
 
163
- describe '#apply_association_changes!' do
164
- let(:profile) { Profile.new first_name: 'Name' }
165
- let(:project) { Project.new title: 'Project' }
166
- let(:user) { User.new(profile: profile, projects: [project]) }
167
- before { project.build_author(name: 'Author') }
168
-
169
- specify do
170
- expect { user.apply_association_changes! }.to change { user.attributes['profile'] }
171
- .from(nil).to('first_name' => 'Name', 'last_name' => nil)
172
- end
173
- specify do
174
- expect { user.apply_association_changes! }.to change { user.attributes['projects'] }
175
- .from(nil).to([{'title' => 'Project', 'author' => {'name' => 'Author'}}])
176
- end
177
-
178
- context do
179
- let(:project) { Project.new }
180
- specify { expect { user.apply_association_changes! }.to raise_error Granite::Form::AssociationChangesNotApplied }
181
- end
182
- end
183
-
184
163
  describe '#instantiate' do
185
164
  before { User.send(:include, Granite::Form::Model::Persistence) }
186
165
  let(:profile) { Profile.new first_name: 'Name' }
@@ -57,51 +57,6 @@ describe Granite::Form::Model::Attributes::Attribute do
57
57
  specify { expect(attribute(default: false, type: Boolean).defaultize(nil)).to eq(false) }
58
58
  end
59
59
 
60
- describe '#typecast' do
61
- context 'when Object' do
62
- specify { expect(attribute.typecast(:hello)).to eq(:hello) }
63
- end
64
-
65
- context 'when Integer' do
66
- specify { expect(attribute(type: Integer).typecast(42)).to eq(42) }
67
- specify { expect(attribute(type: Integer).typecast('42')).to eq(42) }
68
- end
69
-
70
- context 'when Hash' do
71
- let(:to_h) { {'x' => {'foo' => 'bar'}, 'y' => 2} }
72
- let(:parameters) { ActionController::Parameters.new(to_h) }
73
-
74
- before(:all) do
75
- @default_hash_typecaster = Granite::Form.typecaster('Hash')
76
- require 'action_controller'
77
- Class.new(ActionController::Base)
78
- @action_controller_hash_typecaster = Granite::Form.typecaster('Hash')
79
- end
80
-
81
- context 'when ActionController is loaded' do
82
- before { Granite::Form.typecaster('Hash', &@action_controller_hash_typecaster) }
83
- after { Granite::Form.typecaster('Hash', &@default_hash_typecaster) }
84
-
85
- specify { expect(attribute(type: Hash).typecast(nil)).to be_nil }
86
- specify { expect(attribute(type: Hash).typecast(to_h)).to eq(to_h) }
87
- specify { expect(attribute(type: Hash).typecast(parameters)).to be_nil }
88
- specify { expect(attribute(type: Hash).typecast(parameters.permit(:y, x: [:foo]))).to eq(to_h) }
89
- end
90
-
91
- context 'when ActionController is not loaded' do
92
- before { Granite::Form.typecaster('Hash', &@default_hash_typecaster) }
93
-
94
- specify { expect(attribute(type: Hash).typecast(nil)).to be_nil }
95
- specify { expect(attribute(type: Hash).typecast(to_h)).to eq(to_h) }
96
- if ActiveSupport.version > Gem::Version.new('4.3')
97
- specify { expect(attribute(type: Hash).typecast(parameters.permit(:y, x: [:foo]))).to be_nil }
98
- else
99
- specify { expect(attribute(type: Hash).typecast(parameters.permit(:y, x: [:foo]))).to eq(to_h) }
100
- end
101
- end
102
- end
103
- end
104
-
105
60
  describe '#enum' do
106
61
  before { allow_any_instance_of(Dummy).to receive_messages(value: 1..5) }
107
62
 
@@ -144,7 +99,10 @@ describe Granite::Form::Model::Attributes::Attribute do
144
99
 
145
100
  context 'integration' do
146
101
  before do
147
- allow(Granite::Form).to receive_messages(config: Granite::Form::Config.send(:new))
102
+ config = Granite::Form::Config.send(:new)
103
+ config.types.merge! Granite::Form.config.types
104
+ allow(Granite::Form).to receive_messages(config: config)
105
+
148
106
  Granite::Form.normalizer(:strip) { |value, _, _| value.strip }
149
107
  Granite::Form.normalizer(:trim) do |value, options, _attribute|
150
108
  value.first(length || options[:length] || 2)
@@ -1,12 +1,14 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Granite::Form::Model::Attributes::Base do
4
+ let(:model) { Dummy.new }
5
+
4
6
  before { stub_model(:dummy) }
5
7
 
6
8
  def attribute(*args)
7
9
  options = args.extract_options!
8
- Dummy.add_attribute(Granite::Form::Model::Attributes::Reflections::Base, :field, options)
9
- Dummy.new.attribute(:field)
10
+ Dummy.add_attribute(Granite::Form::Model::Attributes::Reflections::Base, :field, options.reverse_merge(type: Object))
11
+ model.attribute(:field)
10
12
  end
11
13
 
12
14
  describe '#read' do
@@ -94,4 +96,11 @@ describe Granite::Form::Model::Attributes::Base do
94
96
  specify { expect(attribute(readonly: -> { false })).not_to be_readonly }
95
97
  specify { expect(attribute(readonly: -> { true })).to be_readonly }
96
98
  end
99
+
100
+ describe '#type_definition' do
101
+ subject { attr.type_definition }
102
+ let(:attr) { attribute(type: String) }
103
+
104
+ it { is_expected.to have_attributes(type: String, reflection: subject.reflection, owner: model) }
105
+ end
97
106
  end
@@ -6,7 +6,7 @@ describe Granite::Form::Model::Attributes::Localized do
6
6
  def attribute(*args)
7
7
  options = args.extract_options!
8
8
  Dummy.add_attribute(Granite::Form::Model::Attributes::Reflections::Localized, :field, options)
9
- described_class.new('field', Dummy.new)
9
+ Dummy.new.attribute(:field)
10
10
  end
11
11
 
12
12
  describe '#read' do
@@ -43,18 +43,6 @@ describe Granite::Form::Model::Attributes::Reflections::Attribute do
43
43
  specify { expect(reflection(default: -> {}).defaultizer).to be_a Proc }
44
44
  end
45
45
 
46
- describe '#typecaster' do
47
- before do
48
- stub_class(:dummy, String)
49
- stub_class(:dummy_dummy, Dummy)
50
- end
51
-
52
- specify { expect(reflection(type: Object).typecaster).to eq(Granite::Form.typecaster(Object)) }
53
- specify { expect(reflection(type: String).typecaster).to eq(Granite::Form.typecaster(String)) }
54
- specify { expect(reflection(type: Dummy).typecaster).to eq(Granite::Form.typecaster(String)) }
55
- specify { expect(reflection(type: DummyDummy).typecaster).to eq(Granite::Form.typecaster(String)) }
56
- end
57
-
58
46
  describe '#enumerizer' do
59
47
  specify { expect(reflection.enumerizer).to be_nil }
60
48
  specify { expect(reflection(enum: 42).enumerizer).to eq(42) }
@@ -36,7 +36,7 @@ describe Granite::Form::Model::Attributes::Reflections::Base do
36
36
  stub_class(:owner)
37
37
  end
38
38
 
39
- let(:reflection) { SomeScope::Borogoves.new(:field) }
39
+ let(:reflection) { SomeScope::Borogoves.new(:field, type: Object) }
40
40
  let(:owner) { Owner.new }
41
41
 
42
42
  specify { expect(reflection.build_attribute(owner, nil)).to be_a(Granite::Form::Model::Attributes::Borogoves) }
@@ -30,7 +30,7 @@ describe Granite::Form::Model::Dirty do
30
30
  let(:other_author) { Author.create!(name: 'Other') }
31
31
 
32
32
  specify { expect(Model.new.changes).to eq({}) }
33
- specify { expect(Model.new.tap { |m| m.create_something(value: 'Value') }.changes).to eq({}) }
33
+ specify { expect(Model.new.tap { |m| m.build_something(value: 'Value') }.changes).to eq({}) }
34
34
 
35
35
  specify { expect(Model.new(author: author).changes).to eq('author_id' => [nil, author.id]) }
36
36
  specify { expect(Model.new(author_id: author.id).changes).to eq('author_id' => [nil, author.id]) }