granite-form 0.1.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.
Files changed (134) hide show
  1. checksums.yaml +7 -0
  2. data/.codeclimate.yml +13 -0
  3. data/.github/workflows/ci.yml +35 -0
  4. data/.github/workflows/main.yml +29 -0
  5. data/.gitignore +21 -0
  6. data/.rspec +2 -0
  7. data/.rubocop.yml +64 -0
  8. data/.rubocop_todo.yml +48 -0
  9. data/Appraisals +8 -0
  10. data/CHANGELOG.md +73 -0
  11. data/Gemfile +8 -0
  12. data/Guardfile +77 -0
  13. data/LICENSE +22 -0
  14. data/README.md +429 -0
  15. data/Rakefile +6 -0
  16. data/gemfiles/rails.4.2.gemfile +15 -0
  17. data/gemfiles/rails.5.0.gemfile +15 -0
  18. data/gemfiles/rails.5.1.gemfile +15 -0
  19. data/gemfiles/rails.5.2.gemfile +15 -0
  20. data/gemfiles/rails.6.0.gemfile +14 -0
  21. data/gemfiles/rails.6.1.gemfile +14 -0
  22. data/gemfiles/rails.7.0.gemfile +14 -0
  23. data/granite-form.gemspec +31 -0
  24. data/lib/granite/form/active_record/associations.rb +57 -0
  25. data/lib/granite/form/active_record/nested_attributes.rb +20 -0
  26. data/lib/granite/form/base.rb +15 -0
  27. data/lib/granite/form/config.rb +42 -0
  28. data/lib/granite/form/errors.rb +111 -0
  29. data/lib/granite/form/extensions.rb +36 -0
  30. data/lib/granite/form/model/associations/base.rb +97 -0
  31. data/lib/granite/form/model/associations/collection/embedded.rb +14 -0
  32. data/lib/granite/form/model/associations/collection/proxy.rb +35 -0
  33. data/lib/granite/form/model/associations/embeds_any.rb +19 -0
  34. data/lib/granite/form/model/associations/embeds_many.rb +152 -0
  35. data/lib/granite/form/model/associations/embeds_one.rb +112 -0
  36. data/lib/granite/form/model/associations/nested_attributes.rb +215 -0
  37. data/lib/granite/form/model/associations/persistence_adapters/active_record/referenced_proxy.rb +33 -0
  38. data/lib/granite/form/model/associations/persistence_adapters/active_record.rb +68 -0
  39. data/lib/granite/form/model/associations/persistence_adapters/base.rb +55 -0
  40. data/lib/granite/form/model/associations/references_any.rb +43 -0
  41. data/lib/granite/form/model/associations/references_many.rb +113 -0
  42. data/lib/granite/form/model/associations/references_one.rb +88 -0
  43. data/lib/granite/form/model/associations/reflections/base.rb +92 -0
  44. data/lib/granite/form/model/associations/reflections/embeds_any.rb +52 -0
  45. data/lib/granite/form/model/associations/reflections/embeds_many.rb +17 -0
  46. data/lib/granite/form/model/associations/reflections/embeds_one.rb +19 -0
  47. data/lib/granite/form/model/associations/reflections/references_any.rb +65 -0
  48. data/lib/granite/form/model/associations/reflections/references_many.rb +30 -0
  49. data/lib/granite/form/model/associations/reflections/references_one.rb +32 -0
  50. data/lib/granite/form/model/associations/reflections/singular.rb +37 -0
  51. data/lib/granite/form/model/associations/validations.rb +41 -0
  52. data/lib/granite/form/model/associations.rb +120 -0
  53. data/lib/granite/form/model/attributes/attribute.rb +75 -0
  54. data/lib/granite/form/model/attributes/base.rb +134 -0
  55. data/lib/granite/form/model/attributes/collection.rb +19 -0
  56. data/lib/granite/form/model/attributes/dictionary.rb +28 -0
  57. data/lib/granite/form/model/attributes/localized.rb +44 -0
  58. data/lib/granite/form/model/attributes/reference_many.rb +21 -0
  59. data/lib/granite/form/model/attributes/reference_one.rb +52 -0
  60. data/lib/granite/form/model/attributes/reflections/attribute.rb +61 -0
  61. data/lib/granite/form/model/attributes/reflections/base.rb +62 -0
  62. data/lib/granite/form/model/attributes/reflections/collection.rb +12 -0
  63. data/lib/granite/form/model/attributes/reflections/dictionary.rb +15 -0
  64. data/lib/granite/form/model/attributes/reflections/localized.rb +45 -0
  65. data/lib/granite/form/model/attributes/reflections/reference_many.rb +12 -0
  66. data/lib/granite/form/model/attributes/reflections/reference_one.rb +49 -0
  67. data/lib/granite/form/model/attributes/reflections/represents.rb +56 -0
  68. data/lib/granite/form/model/attributes/represents.rb +67 -0
  69. data/lib/granite/form/model/attributes.rb +204 -0
  70. data/lib/granite/form/model/callbacks.rb +72 -0
  71. data/lib/granite/form/model/conventions.rb +40 -0
  72. data/lib/granite/form/model/dirty.rb +84 -0
  73. data/lib/granite/form/model/lifecycle.rb +309 -0
  74. data/lib/granite/form/model/localization.rb +26 -0
  75. data/lib/granite/form/model/persistence.rb +59 -0
  76. data/lib/granite/form/model/primary.rb +59 -0
  77. data/lib/granite/form/model/representation.rb +101 -0
  78. data/lib/granite/form/model/scopes.rb +118 -0
  79. data/lib/granite/form/model/validations/associated.rb +22 -0
  80. data/lib/granite/form/model/validations/nested.rb +56 -0
  81. data/lib/granite/form/model/validations.rb +29 -0
  82. data/lib/granite/form/model.rb +33 -0
  83. data/lib/granite/form/railtie.rb +9 -0
  84. data/lib/granite/form/undefined_class.rb +11 -0
  85. data/lib/granite/form/version.rb +5 -0
  86. data/lib/granite/form.rb +163 -0
  87. data/spec/lib/granite/form/active_record/associations_spec.rb +211 -0
  88. data/spec/lib/granite/form/active_record/nested_attributes_spec.rb +15 -0
  89. data/spec/lib/granite/form/config_spec.rb +66 -0
  90. data/spec/lib/granite/form/model/associations/embeds_many_spec.rb +706 -0
  91. data/spec/lib/granite/form/model/associations/embeds_one_spec.rb +533 -0
  92. data/spec/lib/granite/form/model/associations/nested_attributes_spec.rb +119 -0
  93. data/spec/lib/granite/form/model/associations/persistence_adapters/active_record_spec.rb +58 -0
  94. data/spec/lib/granite/form/model/associations/references_many_spec.rb +572 -0
  95. data/spec/lib/granite/form/model/associations/references_one_spec.rb +445 -0
  96. data/spec/lib/granite/form/model/associations/reflections/embeds_any_spec.rb +42 -0
  97. data/spec/lib/granite/form/model/associations/reflections/embeds_many_spec.rb +145 -0
  98. data/spec/lib/granite/form/model/associations/reflections/embeds_one_spec.rb +117 -0
  99. data/spec/lib/granite/form/model/associations/reflections/references_many_spec.rb +303 -0
  100. data/spec/lib/granite/form/model/associations/reflections/references_one_spec.rb +287 -0
  101. data/spec/lib/granite/form/model/associations/validations_spec.rb +137 -0
  102. data/spec/lib/granite/form/model/associations_spec.rb +198 -0
  103. data/spec/lib/granite/form/model/attributes/attribute_spec.rb +186 -0
  104. data/spec/lib/granite/form/model/attributes/base_spec.rb +97 -0
  105. data/spec/lib/granite/form/model/attributes/collection_spec.rb +72 -0
  106. data/spec/lib/granite/form/model/attributes/dictionary_spec.rb +100 -0
  107. data/spec/lib/granite/form/model/attributes/localized_spec.rb +103 -0
  108. data/spec/lib/granite/form/model/attributes/reflections/attribute_spec.rb +72 -0
  109. data/spec/lib/granite/form/model/attributes/reflections/base_spec.rb +56 -0
  110. data/spec/lib/granite/form/model/attributes/reflections/collection_spec.rb +37 -0
  111. data/spec/lib/granite/form/model/attributes/reflections/dictionary_spec.rb +43 -0
  112. data/spec/lib/granite/form/model/attributes/reflections/localized_spec.rb +37 -0
  113. data/spec/lib/granite/form/model/attributes/reflections/represents_spec.rb +70 -0
  114. data/spec/lib/granite/form/model/attributes/represents_spec.rb +85 -0
  115. data/spec/lib/granite/form/model/attributes_spec.rb +350 -0
  116. data/spec/lib/granite/form/model/callbacks_spec.rb +337 -0
  117. data/spec/lib/granite/form/model/conventions_spec.rb +11 -0
  118. data/spec/lib/granite/form/model/dirty_spec.rb +84 -0
  119. data/spec/lib/granite/form/model/lifecycle_spec.rb +356 -0
  120. data/spec/lib/granite/form/model/persistence_spec.rb +46 -0
  121. data/spec/lib/granite/form/model/primary_spec.rb +84 -0
  122. data/spec/lib/granite/form/model/representation_spec.rb +139 -0
  123. data/spec/lib/granite/form/model/scopes_spec.rb +86 -0
  124. data/spec/lib/granite/form/model/typecasting_spec.rb +193 -0
  125. data/spec/lib/granite/form/model/validations/associated_spec.rb +102 -0
  126. data/spec/lib/granite/form/model/validations/nested_spec.rb +164 -0
  127. data/spec/lib/granite/form/model/validations_spec.rb +31 -0
  128. data/spec/lib/granite/form/model_spec.rb +10 -0
  129. data/spec/lib/granite/form_spec.rb +11 -0
  130. data/spec/shared/nested_attribute_examples.rb +332 -0
  131. data/spec/spec_helper.rb +50 -0
  132. data/spec/support/model_helpers.rb +10 -0
  133. data/spec/support/muffle_helper.rb +7 -0
  134. metadata +403 -0
@@ -0,0 +1,211 @@
1
+ require 'spec_helper'
2
+
3
+ describe Granite::Form::ActiveRecord::Associations do
4
+ before do
5
+ stub_model(:project) do
6
+ include Granite::Form::Model::Lifecycle
7
+ include Granite::Form::Model::Associations
8
+
9
+ attribute :title, String
10
+
11
+ validates :title, presence: true
12
+
13
+ embeds_one :author do
14
+ attribute :name, String
15
+
16
+ validates :name, presence: true
17
+ end
18
+ end
19
+
20
+ stub_model(:profile) do
21
+ include Granite::Form::Model::Lifecycle
22
+
23
+ attribute :first_name, String
24
+ attribute :last_name, String
25
+ attribute :admin, Boolean
26
+ end
27
+
28
+ stub_class(:user, ActiveRecord::Base) do
29
+ embeds_many :projects
30
+ embeds_one :profile
31
+
32
+ # Simulating JSON attribute
33
+ serialize :projects, JSON
34
+
35
+ def read_attribute(name)
36
+ if name.to_s == 'projects'
37
+ value = super
38
+ JSON.parse(value) if value
39
+ else
40
+ super
41
+ end
42
+ end
43
+
44
+ validates :projects, associated: true
45
+ end
46
+ end
47
+
48
+ let(:user) { User.new }
49
+
50
+ specify { expect(user.projects).to eq([]) }
51
+ specify { expect(user.profile).to be_nil }
52
+
53
+ context 'new owner' do
54
+ let(:user) { User.new }
55
+
56
+ describe '#projects' do
57
+ specify do
58
+ expect { user.projects << Project.new }
59
+ .not_to change { user.read_attribute(:projects) }
60
+ end
61
+ specify do
62
+ expect { user.projects << Project.new(title: 'First') }
63
+ .not_to change { user.read_attribute(:projects) }
64
+ end
65
+ specify do
66
+ expect { user.projects << Project.new(title: 'First') }
67
+ .not_to change { user.projects.reload.count }
68
+ end
69
+ specify do
70
+ user.projects << Project.new(title: 'First')
71
+ user.save
72
+ expect(user.reload.projects.first.title).to eq('First')
73
+ end
74
+ end
75
+
76
+ describe '#profile' do
77
+ specify do
78
+ expect { user.profile = Profile.new(first_name: 'google.com') }
79
+ .not_to change { user.read_attribute(:profile) }
80
+ end
81
+ specify do
82
+ expect { user.profile = Profile.new(first_name: 'google.com') }
83
+ .to change { user.profile }.from(nil).to(an_instance_of(Profile))
84
+ end
85
+ specify do
86
+ user.profile = Profile.new(first_name: 'google.com')
87
+ user.save
88
+ expect(user.reload.profile.first_name).to eq('google.com')
89
+ end
90
+
91
+ context 'with profile already set' do
92
+ before { user.create_profile(admin: true) }
93
+
94
+ specify do
95
+ user.profile.admin = false
96
+ user.save
97
+ expect(user.reload.profile.admin).to eq(false)
98
+ end
99
+ end
100
+ end
101
+ end
102
+
103
+ context 'persisted owner' do
104
+ let(:user) { User.create }
105
+
106
+ describe '#projects' do
107
+ specify do
108
+ expect { user.projects << Project.new }
109
+ .not_to change { user.read_attribute(:projects) }
110
+ end
111
+ specify do
112
+ expect { user.projects << Project.new(title: 'First') }
113
+ .to change { user.projects.reload.count }.from(0).to(1)
114
+ end
115
+ specify do
116
+ user.projects << Project.new(title: 'First')
117
+ user.save
118
+ expect(user.reload.projects.first.title).to eq('First')
119
+ end
120
+
121
+ context do
122
+ let(:project) { Project.new(title: 'First') }
123
+ before { project.build_author(name: 'Author') }
124
+
125
+ specify do
126
+ expect { user.projects << project }
127
+ .to change { user.read_attribute(:projects) }.from(nil)
128
+ .to([{'title' => 'First', 'author' => {'name' => 'Author'}}])
129
+ end
130
+ specify do
131
+ expect do
132
+ user.projects << project
133
+ user.save
134
+ end
135
+ .to change { user.reload.read_attribute(:projects) }.from(nil)
136
+ .to([{'title' => 'First', 'author' => {'name' => 'Author'}}])
137
+ end
138
+ end
139
+ end
140
+
141
+ describe '#profile' do
142
+ specify do
143
+ expect { user.profile = Profile.new(first_name: 'google.com') }
144
+ .to change { user.profile }.from(nil).to(an_instance_of(Profile))
145
+ end
146
+ specify do
147
+ user.profile = Profile.new(first_name: 'google.com')
148
+ user.save
149
+ expect(user.reload.profile.first_name).to eq('google.com')
150
+ end
151
+ specify do
152
+ expect { user.profile = Profile.new(first_name: 'google.com') }
153
+ .to change { user.read_attribute(:profile) }.from(nil)
154
+ .to({first_name: 'google.com', last_name: nil, admin: nil}.to_json)
155
+ end
156
+ specify do
157
+ expect do
158
+ user.profile = Profile.new(first_name: 'google.com')
159
+ user.save
160
+ end
161
+ .to change { user.reload.read_attribute(:profile) }.from(nil)
162
+ .to({first_name: 'google.com', last_name: nil, admin: nil}.to_json)
163
+ end
164
+ end
165
+ end
166
+
167
+ context 'class determine errors' do
168
+ specify do
169
+ expect do
170
+ stub_class(:book, ActiveRecord::Base) do
171
+ embeds_one :author, class_name: 'Borogoves'
172
+ end.reflect_on_association(:author).klass end.to raise_error NameError
173
+ end
174
+
175
+ specify do
176
+ expect do
177
+ stub_class(:user, ActiveRecord::Base) do
178
+ embeds_many :projects, class_name: 'Borogoves' do
179
+ attribute :title
180
+ end
181
+ end.reflect_on_association(:projects).klass end.to raise_error NameError
182
+ end
183
+ end
184
+
185
+ context 'on the fly' do
186
+ before do
187
+ stub_class(:user, ActiveRecord::Base) do
188
+ embeds_many :projects do
189
+ attribute :title, String
190
+ end
191
+ embeds_one :profile, class_name: 'Profile' do
192
+ attribute :age, Integer
193
+ end
194
+ end
195
+ end
196
+
197
+ specify { expect(User.reflect_on_association(:projects).klass).to eq(User::Project) }
198
+ specify { expect(User.new.projects).to eq([]) }
199
+ specify { expect(User.new.tap { |u| u.projects.create(title: 'Project') }.projects).to be_a(Granite::Form::Model::Associations::Collection::Embedded) }
200
+ specify { expect(User.new.tap { |u| u.projects.create(title: 'Project') }.read_attribute(:projects)).to eq([{title: 'Project'}].to_json) }
201
+
202
+ specify { expect(User.reflect_on_association(:profile).klass).to eq(User::Profile) }
203
+ specify { expect(User.reflect_on_association(:profile).klass).to be < Profile }
204
+ specify { expect(User.new.profile).to be_nil }
205
+ specify { expect(User.new.tap { |u| u.create_profile(first_name: 'Profile') }.profile).to be_a(User::Profile) }
206
+ specify do
207
+ expect(User.new.tap { |u| u.create_profile(first_name: 'Profile') }.read_attribute(:profile))
208
+ .to eq({first_name: 'Profile', last_name: nil, admin: nil, age: nil}.to_json)
209
+ end
210
+ end
211
+ end
@@ -0,0 +1,15 @@
1
+ require 'spec_helper'
2
+ require 'shared/nested_attribute_examples'
3
+
4
+ describe Granite::Form::ActiveRecord::NestedAttributes do
5
+ before do
6
+ stub_class(:user, ActiveRecord::Base) do
7
+ embeds_one :profile
8
+ embeds_many :projects
9
+
10
+ accepts_nested_attributes_for :profile, :projects
11
+ end
12
+ end
13
+
14
+ include_examples 'nested attributes'
15
+ end
@@ -0,0 +1,66 @@
1
+ require 'spec_helper'
2
+
3
+ describe Granite::Form::Config do
4
+ subject { Granite::Form::Config.send :new }
5
+
6
+ describe '#include_root_in_json' do
7
+ its(:include_root_in_json) { should == false }
8
+ specify do
9
+ expect { subject.include_root_in_json = true }
10
+ .to change { subject.include_root_in_json }.from(false).to(true)
11
+ end
12
+ end
13
+
14
+ describe '#i18n_scope' do
15
+ its(:i18n_scope) { should == :granite }
16
+ specify do
17
+ expect { subject.i18n_scope = :data_model }
18
+ .to change { subject.i18n_scope }.from(:granite).to(:data_model)
19
+ end
20
+ end
21
+
22
+ describe '#logger' do
23
+ its(:logger) { should be_a Logger }
24
+ end
25
+
26
+ describe '#primary_attribute' do
27
+ its(:primary_attribute) { should == :id }
28
+ specify do
29
+ expect { subject.primary_attribute = :identified }
30
+ .to change { subject.primary_attribute }.from(:id).to(:identified)
31
+ end
32
+ end
33
+
34
+ describe '#normalizer' do
35
+ specify do
36
+ expect { subject.normalizer(:name) {} }
37
+ .to change {
38
+ begin
39
+ subject.normalizer(:name)
40
+ rescue Granite::Form::NormalizerMissing
41
+ nil
42
+ end
43
+ }.from(nil).to(an_instance_of(Proc))
44
+ end
45
+ specify { expect { subject.normalizer(:wrong) }.to raise_error Granite::Form::NormalizerMissing }
46
+ end
47
+
48
+ describe '#typecaster' do
49
+ specify do
50
+ expect { subject.typecaster('Object') {} }
51
+ .to change { muffle(Granite::Form::TypecasterMissing) { subject.typecaster(Time, Object) } }
52
+ .from(nil).to(an_instance_of(Proc))
53
+ end
54
+ specify do
55
+ expect { subject.typecaster('Object') {} }
56
+ .to change { muffle(Granite::Form::TypecasterMissing) { subject.typecaster('time', 'object') } }
57
+ .from(nil).to(an_instance_of(Proc))
58
+ end
59
+ specify do
60
+ expect { subject.typecaster('Object') {} }
61
+ .to change { muffle(Granite::Form::TypecasterMissing) { subject.typecaster(Object) } }
62
+ .from(nil).to(an_instance_of(Proc))
63
+ end
64
+ specify { expect { subject.typecaster(Object) }.to raise_error Granite::Form::TypecasterMissing }
65
+ end
66
+ end