formstar 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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 38aeef2c7afb9fd595c206197dccbee112de434a90eb9e248ea00ad904aed3c7
4
+ data.tar.gz: 4c32f1b301bc866674cbf5e72a7fe2b22846e5ab40e71c0d39e63e048b55e74d
5
+ SHA512:
6
+ metadata.gz: bf3e731b37d9f73a377d7c62a85058526b43d3f9f0769833e9d9fd7ad043aeb95f2098a088c2c227f6ff8e8b84e52847ce4f4c83aeb43942736962ce68d3df3f
7
+ data.tar.gz: 3e0e00422dcdbbfe41b9aa64117c728abcac3c23e7c049d850e1790f380af6ee3f9bb6d9093b2f62c70237dfbe416527299e3c4d9352ebb36582aa94690958ce
@@ -0,0 +1,243 @@
1
+ require "after_commit_everywhere"
2
+ require "active_model"
3
+ require "active_support"
4
+
5
+ module Formstar
6
+ class Base
7
+ include ActiveModel::Model
8
+ include ActiveModel::Attributes
9
+ include ActiveModel::Validations::Callbacks
10
+ include AfterCommitEverywhere
11
+
12
+ class NoModelError < StandardError; end
13
+
14
+ define_model_callbacks :initialize, only: [:after]
15
+ define_model_callbacks :save, only: [:after]
16
+ define_model_callbacks :commit, only: [:after]
17
+
18
+ attr_reader :form_succeeded
19
+ attr_reader :form_submitted
20
+
21
+ validate :validate_form_model, -> { form_model_defined? && !form_model.nil? }
22
+
23
+ def initialize(...)
24
+ raise NotImplementedError.new("`#{self.class.name}` is abstract and cannot be instantiated directly.") if ["Formstar::Base", "ApplicationForm"].include?(self.class.name)
25
+
26
+ @form_succeeded = nil
27
+ @form_submitted = false
28
+
29
+ run_callbacks(:initialize) do
30
+ super(...)
31
+ end
32
+ end
33
+
34
+ def submit
35
+ raise NotImplementedError
36
+ end
37
+
38
+ def succeeded?
39
+ @form_succeeded
40
+ end
41
+
42
+ def submitted?
43
+ @form_submitted
44
+ end
45
+
46
+ def form_attributes
47
+ return @form_attributes unless @form_attributes.nil?
48
+
49
+ @form_attributes = {}
50
+ self.class.form_attribute_names.each do |attr_name|
51
+ @form_attributes[attr_name] = send(attr_name)
52
+ end
53
+
54
+ @form_attributes
55
+ end
56
+
57
+ def save(**opts)
58
+ save!(**opts)
59
+ rescue
60
+ false
61
+ end
62
+
63
+ def save!(**opts)
64
+ @form_submitted = true
65
+ validate!(context: opts[:context]) unless opts[:validate] == false
66
+
67
+ perform_submit = -> do
68
+ run_callbacks(:save) do
69
+ submit
70
+ end
71
+ end
72
+
73
+ if self.class.config[:submit_within_transaction_enabled]
74
+ ActiveRecord::Base.transaction do
75
+ perform_submit.call
76
+ end
77
+ else
78
+ perform_submit.call
79
+ end
80
+
81
+ after_commit { run_callbacks(:commit) }
82
+
83
+ @form_succeeded = true
84
+ rescue => e
85
+ @form_succeeded = false
86
+ raise e
87
+ end
88
+
89
+ def model
90
+ raise NoModelError.new unless form_model_defined?
91
+ self.form_model
92
+ end
93
+
94
+ def model=(new_model)
95
+ raise NoModelError.new unless form_model_defined?
96
+ self.form_model = new_model
97
+ end
98
+
99
+ private
100
+
101
+ def form_model
102
+ return nil unless form_model_defined?
103
+ send(form_model_name)
104
+ end
105
+
106
+ def form_model=(new_model)
107
+ return nil unless form_model_defined?
108
+ send("#{form_model_name}=".to_sym, new_model)
109
+ end
110
+
111
+ def validate_form_model
112
+ promote_form_model_errors(form_model) if form_model&.invalid?
113
+ end
114
+
115
+ def promote_form_model_errors(form_model)
116
+ form_model.errors.each do |e|
117
+ errors.add(e.attribute, e.type, message: e.message)
118
+ end
119
+ end
120
+
121
+ def form_model_name
122
+ @form_model_name ||= self.class.config[:model].fetch(:name, nil)&.to_sym
123
+ end
124
+
125
+ def form_model_defined?
126
+ @model_defined ||= !self.class.config[:model].nil? #&&
127
+ end
128
+
129
+ def config
130
+ self.class.config
131
+ end
132
+
133
+ def respond_to_missing?(name, include_private = false)
134
+ if form_model_defined? && name.to_sym != self.class.config[:model][:name]
135
+ form_model.respond_to?(name, include_private)
136
+ else
137
+ super
138
+ end
139
+ end
140
+
141
+ def method_missing(method, *args, &block)
142
+ if form_model_defined? && method.to_sym != self.class.config[:model][:name]
143
+ form_model.send(method, *args, &block)
144
+ else
145
+ super
146
+ end
147
+ end
148
+
149
+ class << self
150
+
151
+ attr_reader :config
152
+ attr_reader :form_attribute_names
153
+
154
+ def inherited(subclass)
155
+ subclass.instance_variable_set(:@config, self.config.dup)
156
+ end
157
+
158
+ def config
159
+ @config ||= {
160
+ model_translation_fallback_enabled: true,
161
+ model_translation_fallback_class_name: nil,
162
+ submit_within_transaction_enabled: true,
163
+ model: nil
164
+ }
165
+ end
166
+
167
+ def model_translation_fallback(enabled, class_name: nil)
168
+ config[:model_translation_fallback_enabled] = enabled
169
+ config[:model_translation_fallback_class_name] = class_name&.safe_constantize
170
+ end
171
+
172
+ def submit_within_transaction(enabled)
173
+ config[:submit_within_transaction_enabled] = enabled
174
+ end
175
+
176
+ def attr_form(name, ...)
177
+ form_attribute_names.add(name.to_sym)
178
+ attribute(name, ...)
179
+ end
180
+
181
+ def with_model(name, class_name: name)
182
+ name = name.to_sym
183
+ class_name = class_name.to_s.classify
184
+ klass = Object.const_get(class_name)
185
+
186
+ # Don't accept models that are modules.
187
+ raise ArgumentError.new("Expected an ActiveRecord model, got `#{class_name}`") unless klass < ActiveRecord::Base
188
+
189
+ config[:model] = { name: name, klass: klass }
190
+ end
191
+
192
+ def model_name
193
+ return @model_name unless @model_name.nil?
194
+
195
+ if config[:model]
196
+ @model_name = ActiveModel::Name.new(config[:model][:klass])
197
+ @model_name.i18n_key = self.name.underscore
198
+ else
199
+ @model_name = ActiveModel::Name.new(self)
200
+ end
201
+
202
+ @model_name
203
+ end
204
+
205
+ def form_attribute_names
206
+ @form_attribute_names ||= Set.new
207
+ end
208
+
209
+ def i18n_scope
210
+ :form
211
+ end
212
+
213
+ def human_attribute_name(attribute, options = {})
214
+ # Base errors for a form will never fall back to the underlying model.
215
+ # So we can safely always delegate the translation lookup back to active model
216
+ # which will build up the correct lookup paths.
217
+ return attribute if attribute.to_sym == :base
218
+
219
+ I18n.t!("#{i18n_scope}.attributes.#{model_name.i18n_key}.#{attribute}")
220
+ rescue I18n::MissingTranslationData => e
221
+ fallback_value = nil
222
+
223
+ if config[:model_translation_fallback_enabled]
224
+ if config[:model]
225
+ form_model_klass = config[:model][:klass]
226
+ else
227
+ form_model_klass = nil
228
+ end
229
+
230
+ fallback_klass = config[:model_translation_fallback_class_name] || form_model_klass
231
+ fallback_value = fallback_klass.human_attribute_name(attribute, options) if fallback_klass
232
+ end
233
+
234
+ if fallback_value
235
+ fallback_value
236
+ else
237
+ raise e
238
+ end
239
+ end
240
+ end
241
+
242
+ end
243
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Formstar
4
+ VERSION = "0.1.0"
5
+ end
data/lib/formstar.rb ADDED
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'formstar/base'
4
+ require 'generators/formstar/install/install_generator'
@@ -0,0 +1,14 @@
1
+ require "rails/generators"
2
+
3
+ module Formstar
4
+ module Generators
5
+ class InstallGenerator < Rails::Generators::Base
6
+ source_root File.expand_path("templates", __dir__)
7
+
8
+ def copy_application_enum
9
+ template "app/forms/application_form.rb", "app/forms/application_form.rb"
10
+ end
11
+
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,4 @@
1
+ class ApplicationForm < Formstar::Base
2
+ model_translation_fallback true
3
+ submit_within_transaction true
4
+ end
metadata ADDED
@@ -0,0 +1,114 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: formstar
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Bevan Holborn
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 2025-01-17 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: rake
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '0'
19
+ type: :development
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '0'
26
+ - !ruby/object:Gem::Dependency
27
+ name: rspec
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: '3.0'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '3.0'
40
+ - !ruby/object:Gem::Dependency
41
+ name: after_commit_everywhere
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '1.4'
47
+ type: :runtime
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '1.4'
54
+ - !ruby/object:Gem::Dependency
55
+ name: activemodel
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '5.0'
61
+ type: :runtime
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '5.0'
68
+ - !ruby/object:Gem::Dependency
69
+ name: activesupport
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: '5.0'
75
+ type: :runtime
76
+ prerelease: false
77
+ version_requirements: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: '5.0'
82
+ description: ActiveModel backed Form objects for Ruby on Rails applications
83
+ executables: []
84
+ extensions: []
85
+ extra_rdoc_files: []
86
+ files:
87
+ - lib/formstar.rb
88
+ - lib/formstar/base.rb
89
+ - lib/formstar/version.rb
90
+ - lib/generators/formstar/install/install_generator.rb
91
+ - lib/generators/formstar/install/templates/app/forms/application_form.rb
92
+ homepage: https://rubygems.org/gems/formstar
93
+ licenses:
94
+ - MIT
95
+ metadata:
96
+ source_code_uri: https://gitlab.com/BevanHolborn/formstar
97
+ rdoc_options: []
98
+ require_paths:
99
+ - lib
100
+ required_ruby_version: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - ">="
103
+ - !ruby/object:Gem::Version
104
+ version: 2.7.0
105
+ required_rubygems_version: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - ">="
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ requirements: []
111
+ rubygems_version: 3.6.2
112
+ specification_version: 4
113
+ summary: ActiveModel backed Form objects for Ruby on Rails applications
114
+ test_files: []