blanks 1.0.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 (92) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +5 -0
  3. data/LICENSE.txt +21 -0
  4. data/README.md +427 -0
  5. data/Rakefile +8 -0
  6. data/examples/advanced_features_example.rb +81 -0
  7. data/examples/assign_from_model_example.rb +54 -0
  8. data/examples/model_name_example.rb +31 -0
  9. data/examples/normalization_example.rb +39 -0
  10. data/examples/post_form_example.rb +52 -0
  11. data/lib/blanks/association_proxy.rb +92 -0
  12. data/lib/blanks/associations.rb +58 -0
  13. data/lib/blanks/base.rb +256 -0
  14. data/lib/blanks/model_naming.rb +24 -0
  15. data/lib/blanks/nested_attributes.rb +131 -0
  16. data/lib/blanks/normalization.rb +38 -0
  17. data/lib/blanks/version.rb +3 -0
  18. data/lib/blanks.rb +18 -0
  19. data/spec/blanks/association_proxy_spec.rb +214 -0
  20. data/spec/blanks/associations_spec.rb +185 -0
  21. data/spec/blanks/attributes_extraction_spec.rb +138 -0
  22. data/spec/blanks/base_spec.rb +361 -0
  23. data/spec/blanks/callbacks_spec.rb +60 -0
  24. data/spec/blanks/custom_primary_key_spec.rb +168 -0
  25. data/spec/blanks/dirty_tracking_spec.rb +61 -0
  26. data/spec/blanks/i18n_spec.rb +33 -0
  27. data/spec/blanks/id_tracking_spec.rb +168 -0
  28. data/spec/blanks/inherit_attributes_from_spec.rb +148 -0
  29. data/spec/blanks/inherit_validations_from_spec.rb +260 -0
  30. data/spec/blanks/model_naming_spec.rb +82 -0
  31. data/spec/blanks/nested_attributes_spec.rb +378 -0
  32. data/spec/blanks/normalization_spec.rb +122 -0
  33. data/spec/dummy/Gemfile +10 -0
  34. data/spec/dummy/Gemfile.lock +242 -0
  35. data/spec/dummy/Rakefile +5 -0
  36. data/spec/dummy/app/controllers/application_controller.rb +4 -0
  37. data/spec/dummy/app/controllers/simple_form/articles_controller.rb +65 -0
  38. data/spec/dummy/app/controllers/simple_form/posts_controller.rb +59 -0
  39. data/spec/dummy/app/controllers/standard/articles_controller.rb +65 -0
  40. data/spec/dummy/app/controllers/standard/posts_controller.rb +59 -0
  41. data/spec/dummy/app/forms/article_form.rb +17 -0
  42. data/spec/dummy/app/forms/cover_image_form.rb +9 -0
  43. data/spec/dummy/app/forms/post_form.rb +10 -0
  44. data/spec/dummy/app/forms/tag_form.rb +12 -0
  45. data/spec/dummy/app/models/application_record.rb +5 -0
  46. data/spec/dummy/app/models/article.rb +11 -0
  47. data/spec/dummy/app/models/cover_image.rb +7 -0
  48. data/spec/dummy/app/models/post.rb +8 -0
  49. data/spec/dummy/app/models/tag.rb +7 -0
  50. data/spec/dummy/app/views/layouts/application.html.erb +43 -0
  51. data/spec/dummy/app/views/simple_form/articles/_form.html.erb +43 -0
  52. data/spec/dummy/app/views/simple_form/articles/edit.html.erb +5 -0
  53. data/spec/dummy/app/views/simple_form/articles/index.html.erb +29 -0
  54. data/spec/dummy/app/views/simple_form/articles/new.html.erb +5 -0
  55. data/spec/dummy/app/views/simple_form/articles/show.html.erb +29 -0
  56. data/spec/dummy/app/views/simple_form/posts/_form.html.erb +19 -0
  57. data/spec/dummy/app/views/simple_form/posts/edit.html.erb +5 -0
  58. data/spec/dummy/app/views/simple_form/posts/index.html.erb +27 -0
  59. data/spec/dummy/app/views/simple_form/posts/new.html.erb +5 -0
  60. data/spec/dummy/app/views/simple_form/posts/show.html.erb +12 -0
  61. data/spec/dummy/app/views/standard/articles/_form.html.erb +61 -0
  62. data/spec/dummy/app/views/standard/articles/edit.html.erb +5 -0
  63. data/spec/dummy/app/views/standard/articles/index.html.erb +29 -0
  64. data/spec/dummy/app/views/standard/articles/new.html.erb +5 -0
  65. data/spec/dummy/app/views/standard/articles/show.html.erb +29 -0
  66. data/spec/dummy/app/views/standard/posts/_form.html.erb +30 -0
  67. data/spec/dummy/app/views/standard/posts/edit.html.erb +5 -0
  68. data/spec/dummy/app/views/standard/posts/index.html.erb +27 -0
  69. data/spec/dummy/app/views/standard/posts/new.html.erb +5 -0
  70. data/spec/dummy/app/views/standard/posts/show.html.erb +12 -0
  71. data/spec/dummy/bin/rails +6 -0
  72. data/spec/dummy/config/application.rb +18 -0
  73. data/spec/dummy/config/boot.rb +5 -0
  74. data/spec/dummy/config/database.yml +12 -0
  75. data/spec/dummy/config/environment.rb +5 -0
  76. data/spec/dummy/config/environments/development.rb +9 -0
  77. data/spec/dummy/config/environments/test.rb +8 -0
  78. data/spec/dummy/config/initializers/simple_form.rb +21 -0
  79. data/spec/dummy/config/routes.rb +15 -0
  80. data/spec/dummy/config/storage.yml +3 -0
  81. data/spec/dummy/config.ru +5 -0
  82. data/spec/dummy/db/migrate/1_create_posts.rb +12 -0
  83. data/spec/dummy/db/migrate/2_create_articles.rb +12 -0
  84. data/spec/dummy/db/migrate/3_create_cover_images.rb +12 -0
  85. data/spec/dummy/db/migrate/4_create_tags.rb +12 -0
  86. data/spec/dummy/db/migrate/5_create_active_storage_tables.rb +36 -0
  87. data/spec/dummy/db/schema.rb +82 -0
  88. data/spec/dummy/spec/examples.txt +145 -0
  89. data/spec/dummy/tmp/local_secret.txt +1 -0
  90. data/spec/examples.txt +157 -0
  91. data/spec/spec_helper.rb +21 -0
  92. metadata +159 -0
@@ -0,0 +1,168 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ RSpec.describe "ID tracking in nested forms" do
6
+ describe "AssociationProxy#find_by_id" do
7
+ it "finds form by id" do
8
+ form_class = Class.new(Blanks::Base) do
9
+ attribute :id, :integer
10
+ attribute :url, :string
11
+ end
12
+ stub_const("ImageForm", form_class)
13
+
14
+ proxy = Blanks::AssociationProxy.new("ImageForm")
15
+ image = proxy.new(id: 123, url: "test.jpg")
16
+
17
+ expect(proxy.find_by_id(123)).to eq(image)
18
+ end
19
+
20
+ it "returns nil when id not found" do
21
+ form_class = Class.new(Blanks::Base) do
22
+ attribute :id, :integer
23
+ attribute :url, :string
24
+ end
25
+ stub_const("ImageForm", form_class)
26
+
27
+ proxy = Blanks::AssociationProxy.new("ImageForm")
28
+
29
+ expect(proxy.find_by_id(999)).to be_nil
30
+ end
31
+
32
+ it "handles string and integer ids" do
33
+ form_class = Class.new(Blanks::Base) do
34
+ attribute :id, :integer
35
+ attribute :url, :string
36
+ end
37
+ stub_const("ImageForm", form_class)
38
+
39
+ proxy = Blanks::AssociationProxy.new("ImageForm")
40
+ image = proxy.new(id: 123, url: "test.jpg")
41
+
42
+ expect(proxy.find_by_id("123")).to eq(image)
43
+ end
44
+ end
45
+
46
+ describe "updating existing nested forms by id" do
47
+ it "updates existing form when id matches" do
48
+ image_form_class = Class.new(Blanks::Base) do
49
+ attribute :id, :integer
50
+ attribute :url, :string
51
+ end
52
+ stub_const("ImageForm", image_form_class)
53
+
54
+ form_class = Class.new(Blanks::Base) do
55
+ has_many :images
56
+ attribute :title, :string
57
+ end
58
+
59
+ form = form_class.new(title: "test")
60
+ form.images.new(id: 1, url: "original.jpg")
61
+ form.images.new(id: 2, url: "another.jpg")
62
+
63
+ form.images_attributes = [
64
+ { id: 1, url: "updated.jpg" }
65
+ ]
66
+
67
+ expect(form.images[0].url).to eq("updated.jpg")
68
+ end
69
+
70
+ it "creates new form when id not found" do
71
+ image_form_class = Class.new(Blanks::Base) do
72
+ attribute :id, :integer
73
+ attribute :url, :string
74
+ end
75
+ stub_const("ImageForm", image_form_class)
76
+
77
+ form_class = Class.new(Blanks::Base) do
78
+ has_many :images
79
+ attribute :title, :string
80
+ end
81
+
82
+ form = form_class.new(title: "test")
83
+ form.images.new(id: 1, url: "original.jpg")
84
+
85
+ form.images_attributes = [
86
+ { id: 999, url: "new.jpg" }
87
+ ]
88
+
89
+ expect(form.images.count).to eq(2)
90
+ end
91
+
92
+ it "creates new form when no id provided" do
93
+ image_form_class = Class.new(Blanks::Base) do
94
+ attribute :id, :integer
95
+ attribute :url, :string
96
+ end
97
+ stub_const("ImageForm", image_form_class)
98
+
99
+ form_class = Class.new(Blanks::Base) do
100
+ has_many :images
101
+ attribute :title, :string
102
+ end
103
+
104
+ form = form_class.new(title: "test")
105
+ form.images.new(id: 1, url: "original.jpg")
106
+
107
+ form.images_attributes = [
108
+ { url: "new.jpg" }
109
+ ]
110
+
111
+ expect(form.images.count).to eq(2)
112
+ end
113
+
114
+ it "preserves unchanged forms" do
115
+ image_form_class = Class.new(Blanks::Base) do
116
+ attribute :id, :integer
117
+ attribute :url, :string
118
+ end
119
+ stub_const("ImageForm", image_form_class)
120
+
121
+ form_class = Class.new(Blanks::Base) do
122
+ has_many :images
123
+ attribute :title, :string
124
+ end
125
+
126
+ form = form_class.new(title: "test")
127
+ form.images.new(id: 1, url: "first.jpg")
128
+ form.images.new(id: 2, url: "second.jpg")
129
+
130
+ form.images_attributes = [
131
+ { id: 1, url: "updated.jpg" }
132
+ ]
133
+
134
+ expect(form.images.count).to eq(2)
135
+ expect(form.images[0].url).to eq("updated.jpg")
136
+ expect(form.images[1].url).to eq("second.jpg")
137
+ end
138
+
139
+ it "works with from_model then update" do
140
+ model_image1 = Struct.new(:id, :url).new(1, "model1.jpg")
141
+ model_image2 = Struct.new(:id, :url).new(2, "model2.jpg")
142
+ model = Struct.new(:title, :images).new("test", [model_image1, model_image2])
143
+
144
+ image_form_class = Class.new(Blanks::Base) do
145
+ attribute :id, :integer
146
+ attribute :url, :string
147
+ end
148
+ stub_const("ImageForm", image_form_class)
149
+
150
+ form_class = Class.new(Blanks::Base) do
151
+ has_many :images
152
+ attribute :title, :string
153
+ end
154
+
155
+ form = form_class.from_model(model)
156
+
157
+ form.images_attributes = [
158
+ { id: 1, url: "updated1.jpg" },
159
+ { url: "new.jpg" }
160
+ ]
161
+
162
+ expect(form.images.count).to eq(3)
163
+ expect(form.images[0].url).to eq("updated1.jpg")
164
+ expect(form.images[1].url).to eq("model2.jpg")
165
+ expect(form.images[2].url).to eq("new.jpg")
166
+ end
167
+ end
168
+ end
@@ -0,0 +1,148 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ RSpec.describe "Blanks::Base.inherit_attributes_from" do
6
+ before(:all) do
7
+ @model_class = Class.new do
8
+ include ActiveModel::Model
9
+ include ActiveModel::Attributes
10
+
11
+ attribute :id, :integer
12
+ attribute :title, :string
13
+ attribute :content, :string
14
+ attribute :published_at, :datetime
15
+ attribute :view_count, :integer
16
+ attribute :created_at, :datetime
17
+ attribute :updated_at, :datetime
18
+
19
+ def self.name
20
+ "Post"
21
+ end
22
+ end
23
+ end
24
+
25
+ describe "with no options" do
26
+ it "inherits all attributes from model class" do
27
+ model_class = @model_class
28
+ form_class = Class.new(Blanks::Base) do
29
+ inherit_attributes_from model_class
30
+ end
31
+
32
+ expect(form_class.attribute_names).to include("id", "title", "content", "published_at", "view_count", "created_at", "updated_at")
33
+ end
34
+
35
+ it "preserves attribute types from model class" do
36
+ model_class = @model_class
37
+ form_class = Class.new(Blanks::Base) do
38
+ inherit_attributes_from model_class
39
+ end
40
+
41
+ form = form_class.new(title: "test", view_count: "42")
42
+
43
+ expect(form.title).to eq("test")
44
+ expect(form.view_count).to eq(42)
45
+ end
46
+ end
47
+
48
+ describe "with only: option" do
49
+ it "inherits only specified attributes" do
50
+ model_class = @model_class
51
+ form_class = Class.new(Blanks::Base) do
52
+ inherit_attributes_from model_class, only: [:title, :content]
53
+ end
54
+
55
+ expect(form_class.attribute_names).to include("title", "content")
56
+ expect(form_class.attribute_names).not_to include("id", "created_at", "updated_at")
57
+ end
58
+
59
+ it "works with single attribute" do
60
+ model_class = @model_class
61
+ form_class = Class.new(Blanks::Base) do
62
+ inherit_attributes_from model_class, only: :title
63
+ end
64
+
65
+ expect(form_class.attribute_names).to eq(["title"])
66
+ end
67
+ end
68
+
69
+ describe "with except: option" do
70
+ it "inherits all attributes except specified ones" do
71
+ model_class = @model_class
72
+ form_class = Class.new(Blanks::Base) do
73
+ inherit_attributes_from model_class, except: [:created_at, :updated_at]
74
+ end
75
+
76
+ expect(form_class.attribute_names).to include("id", "title", "content", "published_at", "view_count")
77
+ expect(form_class.attribute_names).not_to include("created_at", "updated_at")
78
+ end
79
+
80
+ it "works with single attribute" do
81
+ model_class = @model_class
82
+ form_class = Class.new(Blanks::Base) do
83
+ inherit_attributes_from model_class, except: :id
84
+ end
85
+
86
+ expect(form_class.attribute_names).to include("title", "content")
87
+ expect(form_class.attribute_names).not_to include("id")
88
+ end
89
+ end
90
+
91
+ describe "validation" do
92
+ it "raises error when both only and except are specified" do
93
+ model_class = @model_class
94
+ expect {
95
+ Class.new(Blanks::Base) do
96
+ inherit_attributes_from model_class, only: [:title], except: [:id]
97
+ end
98
+ }.to raise_error(ArgumentError, "cannot specify both :only and :except")
99
+ end
100
+
101
+ it "raises error when model class does not respond to attribute_types or columns" do
102
+ invalid_class = Class.new
103
+
104
+ expect {
105
+ Class.new(Blanks::Base) do
106
+ inherit_attributes_from invalid_class
107
+ end
108
+ }.to raise_error(ArgumentError, /does not respond to :attribute_types or :columns/)
109
+ end
110
+ end
111
+
112
+ describe "integration with existing attributes" do
113
+ it "does not override existing attribute definitions" do
114
+ model_class = @model_class
115
+ form_class = Class.new(Blanks::Base) do
116
+ attribute :title, :integer
117
+ inherit_attributes_from model_class, only: [:title, :content]
118
+ end
119
+
120
+ expect(form_class.attribute_types["title"].type).to eq(:integer)
121
+ end
122
+
123
+ it "adds new attributes alongside existing ones" do
124
+ model_class = @model_class
125
+ form_class = Class.new(Blanks::Base) do
126
+ attribute :custom_field, :string
127
+ inherit_attributes_from model_class, only: [:title, :content]
128
+ end
129
+
130
+ expect(form_class.attribute_names).to include("custom_field", "title", "content")
131
+ end
132
+ end
133
+
134
+ describe "integration with from_model" do
135
+ it "loads values from model for inherited attributes" do
136
+ model_class = @model_class
137
+ form_class = Class.new(Blanks::Base) do
138
+ inherit_attributes_from model_class, only: [:title, :content]
139
+ end
140
+
141
+ model = model_class.new(title: "Test Post", content: "Test Content")
142
+ form = form_class.from_model(model)
143
+
144
+ expect(form.title).to eq("Test Post")
145
+ expect(form.content).to eq("Test Content")
146
+ end
147
+ end
148
+ end
@@ -0,0 +1,260 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ RSpec.describe "Blanks::Base.inherit_validations_from" do
6
+ before(:all) do
7
+ @model_class = Class.new do
8
+ include ActiveModel::Model
9
+ include ActiveModel::Attributes
10
+ include ActiveModel::Validations
11
+
12
+ attribute :id, :integer
13
+ attribute :title, :string
14
+ attribute :content, :string
15
+ attribute :email, :string
16
+
17
+ validates :title, presence: true
18
+ validates :content, presence: true, length: { minimum: 10 }
19
+ validates :email, format: { with: /\A[^@\s]+@[^@\s]+\z/ }
20
+
21
+ def self.name
22
+ "Post"
23
+ end
24
+ end
25
+
26
+ @model_with_conditionals = Class.new do
27
+ include ActiveModel::Model
28
+ include ActiveModel::Attributes
29
+ include ActiveModel::Validations
30
+
31
+ attribute :title, :string
32
+ attribute :content, :string
33
+ attribute :status, :string
34
+
35
+ validates :title, presence: true
36
+ validates :content, presence: true, if: -> { status == "published" }
37
+ validates :status, presence: true, if: :published?
38
+
39
+ def published?
40
+ status == "published"
41
+ end
42
+
43
+ def self.name
44
+ "ConditionalPost"
45
+ end
46
+ end
47
+ end
48
+
49
+ describe "with no options" do
50
+ it "inherits all validators from model class" do
51
+ model_class = @model_class
52
+ form_class = Class.new(Blanks::Base) do
53
+ model_name_for :test
54
+ attribute :title, :string
55
+ attribute :content, :string
56
+ attribute :email, :string
57
+ inherit_validations_from model_class
58
+ end
59
+
60
+ form = form_class.new
61
+ form.valid?
62
+
63
+ expect(form.errors[:title]).to include("can't be blank")
64
+ expect(form.errors[:content]).to include("can't be blank")
65
+ end
66
+
67
+ it "preserves validator options" do
68
+ model_class = @model_class
69
+ form_class = Class.new(Blanks::Base) do
70
+ model_name_for :test
71
+ attribute :title, :string
72
+ attribute :content, :string
73
+ inherit_validations_from model_class, only: [:content]
74
+ end
75
+
76
+ form = form_class.new(content: "short")
77
+ form.valid?
78
+
79
+ expect(form.errors[:content]).to include("is too short (minimum is 10 characters)")
80
+ end
81
+
82
+ it "inherits format validators" do
83
+ model_class = @model_class
84
+ form_class = Class.new(Blanks::Base) do
85
+ model_name_for :test
86
+ attribute :email, :string
87
+ inherit_validations_from model_class, only: [:email]
88
+ end
89
+
90
+ form = form_class.new(email: "invalid")
91
+ form.valid?
92
+
93
+ expect(form.errors[:email]).to include("is invalid")
94
+ end
95
+ end
96
+
97
+ describe "with only: option" do
98
+ it "inherits only specified validators" do
99
+ model_class = @model_class
100
+ form_class = Class.new(Blanks::Base) do
101
+ model_name_for :test
102
+ attribute :title, :string
103
+ attribute :content, :string
104
+ inherit_validations_from model_class, only: [:title]
105
+ end
106
+
107
+ form = form_class.new
108
+ form.valid?
109
+
110
+ expect(form.errors[:title]).to include("can't be blank")
111
+ expect(form.errors[:content]).to be_empty
112
+ end
113
+
114
+ it "works with single attribute" do
115
+ model_class = @model_class
116
+ form_class = Class.new(Blanks::Base) do
117
+ model_name_for :test
118
+ attribute :title, :string
119
+ inherit_validations_from model_class, only: :title
120
+ end
121
+
122
+ form = form_class.new
123
+ form.valid?
124
+
125
+ expect(form.errors[:title]).to include("can't be blank")
126
+ end
127
+ end
128
+
129
+ describe "with except: option" do
130
+ it "inherits all validators except specified ones" do
131
+ model_class = @model_class
132
+ form_class = Class.new(Blanks::Base) do
133
+ model_name_for :test
134
+ attribute :title, :string
135
+ attribute :content, :string
136
+ attribute :email, :string
137
+ inherit_validations_from model_class, except: [:content, :email]
138
+ end
139
+
140
+ form = form_class.new
141
+ form.valid?
142
+
143
+ expect(form.errors[:title]).to include("can't be blank")
144
+ expect(form.errors[:content]).to be_empty
145
+ expect(form.errors[:email]).to be_empty
146
+ end
147
+
148
+ it "works with single attribute" do
149
+ model_class = @model_class
150
+ form_class = Class.new(Blanks::Base) do
151
+ model_name_for :test
152
+ attribute :title, :string
153
+ attribute :content, :string
154
+ inherit_validations_from model_class, except: [:content, :email]
155
+ end
156
+
157
+ form = form_class.new
158
+ form.valid?
159
+
160
+ expect(form.errors[:title]).to include("can't be blank")
161
+ expect(form.errors[:content]).to be_empty
162
+ end
163
+ end
164
+
165
+ describe "validation" do
166
+ it "raises error when both only and except are specified" do
167
+ model_class = @model_class
168
+ expect {
169
+ Class.new(Blanks::Base) do
170
+ inherit_validations_from model_class, only: [:title], except: [:content]
171
+ end
172
+ }.to raise_error(ArgumentError, "cannot specify both :only and :except")
173
+ end
174
+ end
175
+
176
+ describe "skipping unsafe validators" do
177
+ it "skips validators with proc conditionals" do
178
+ model_class = @model_with_conditionals
179
+ form_class = Class.new(Blanks::Base) do
180
+ model_name_for :test
181
+ attribute :title, :string
182
+ attribute :content, :string
183
+ attribute :status, :string
184
+ inherit_validations_from model_class
185
+ end
186
+
187
+ validators = form_class.validators.map { |v| [v.attributes, v.kind] }
188
+
189
+ expect(validators).to include([[:title], :presence])
190
+ expect(validators).to include([[:status], :presence])
191
+ expect(validators).not_to include([[:content], :presence])
192
+ end
193
+
194
+ it "inherits validators with symbol conditionals" do
195
+ model_class = @model_with_conditionals
196
+ form_class = Class.new(Blanks::Base) do
197
+ model_name_for :test
198
+ attribute :status, :string
199
+ inherit_validations_from model_class, only: [:status]
200
+
201
+ def published?
202
+ status == "published"
203
+ end
204
+ end
205
+
206
+ form = form_class.new(status: nil)
207
+ form.valid?
208
+ expect(form.errors[:status]).to be_empty
209
+
210
+ form_published = form_class.new(status: "published")
211
+ form_published.instance_variable_set(:@status, nil)
212
+ form_published.valid?
213
+ end
214
+ end
215
+
216
+ describe "integration with inherit_attributes_from" do
217
+ it "works with inherited attributes" do
218
+ model_class = @model_class
219
+ form_class = Class.new(Blanks::Base) do
220
+ model_name_for :test
221
+ inherit_attributes_from model_class, only: [:title, :content]
222
+ inherit_validations_from model_class, only: [:title]
223
+ end
224
+
225
+ form = form_class.new
226
+ form.valid?
227
+
228
+ expect(form.errors[:title]).to include("can't be blank")
229
+ expect(form.errors[:content]).to be_empty
230
+ end
231
+ end
232
+
233
+ describe "multiple calls" do
234
+ it "accumulates validators from multiple sources" do
235
+ model_class = @model_class
236
+ other_model = Class.new do
237
+ include ActiveModel::Model
238
+ include ActiveModel::Attributes
239
+ include ActiveModel::Validations
240
+
241
+ attribute :author, :string
242
+ validates :author, presence: true
243
+ end
244
+
245
+ form_class = Class.new(Blanks::Base) do
246
+ model_name_for :test
247
+ attribute :title, :string
248
+ attribute :author, :string
249
+ inherit_validations_from model_class, only: [:title]
250
+ inherit_validations_from other_model, only: [:author]
251
+ end
252
+
253
+ form = form_class.new
254
+ form.valid?
255
+
256
+ expect(form.errors[:title]).to include("can't be blank")
257
+ expect(form.errors[:author]).to include("can't be blank")
258
+ end
259
+ end
260
+ end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ RSpec.describe Blanks::ModelNaming do
6
+ describe ".model_name" do
7
+ it "strips Form suffix from class name" do
8
+ form_class = Class.new(Blanks::Base)
9
+ stub_const("PostForm", form_class)
10
+
11
+ expect(PostForm.model_name.name).to eq("Post")
12
+ end
13
+
14
+ it "generates correct param_key" do
15
+ form_class = Class.new(Blanks::Base)
16
+ stub_const("PostForm", form_class)
17
+
18
+ expect(PostForm.model_name.param_key).to eq("post")
19
+ end
20
+
21
+ it "generates correct route_key" do
22
+ form_class = Class.new(Blanks::Base)
23
+ stub_const("PostForm", form_class)
24
+
25
+ expect(PostForm.model_name.route_key).to eq("posts")
26
+ end
27
+
28
+ it "handles class names without Form suffix" do
29
+ form_class = Class.new(Blanks::Base)
30
+ stub_const("Article", form_class)
31
+
32
+ expect(Article.model_name.name).to eq("Article")
33
+ end
34
+ end
35
+
36
+ describe ".model_name_for" do
37
+ it "overrides the model name" do
38
+ form_class = Class.new(Blanks::Base) do
39
+ model_name_for :article
40
+ end
41
+ stub_const("AdminPostForm", form_class)
42
+
43
+ expect(AdminPostForm.model_name.name).to eq("Article")
44
+ end
45
+
46
+ it "generates correct param_key from override" do
47
+ form_class = Class.new(Blanks::Base) do
48
+ model_name_for :article
49
+ end
50
+ stub_const("AdminPostForm", form_class)
51
+
52
+ expect(AdminPostForm.model_name.param_key).to eq("article")
53
+ end
54
+
55
+ it "accepts symbol" do
56
+ form_class = Class.new(Blanks::Base) do
57
+ model_name_for :blog_post
58
+ end
59
+
60
+ expect(form_class.model_name.name).to eq("BlogPost")
61
+ end
62
+
63
+ it "accepts string" do
64
+ form_class = Class.new(Blanks::Base) do
65
+ model_name_for "blog_post"
66
+ end
67
+
68
+ expect(form_class.model_name.name).to eq("BlogPost")
69
+ end
70
+ end
71
+
72
+ describe "#model_name" do
73
+ it "delegates to class method" do
74
+ form_class = Class.new(Blanks::Base)
75
+ stub_const("PostForm", form_class)
76
+
77
+ form = PostForm.new
78
+
79
+ expect(form.model_name.name).to eq("Post")
80
+ end
81
+ end
82
+ end