well_formed 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 +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +12 -0
- data/.standard.yml +3 -0
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +132 -0
- data/LICENSE.txt +21 -0
- data/README.md +869 -0
- data/Rakefile +36 -0
- data/Steepfile +17 -0
- data/gems/well_formed-pundit/.rspec +3 -0
- data/gems/well_formed-pundit/Gemfile +13 -0
- data/gems/well_formed-pundit/README.md +73 -0
- data/gems/well_formed-pundit/Rakefile +12 -0
- data/gems/well_formed-pundit/lib/well_formed/pundit/version.rb +7 -0
- data/gems/well_formed-pundit/lib/well_formed/pundit.rb +25 -0
- data/gems/well_formed-pundit/lib/well_formed-pundit.rb +8 -0
- data/gems/well_formed-pundit/spec/spec_helper.rb +13 -0
- data/gems/well_formed-pundit/spec/well_formed/pundit_integration_spec.rb +101 -0
- data/gems/well_formed-pundit/spec/well_formed/pundit_spec.rb +80 -0
- data/gems/well_formed-pundit/well_formed-pundit.gemspec +28 -0
- data/lib/generators/resource_form_generator.rb +26 -0
- data/lib/generators/templates/form.rb.tt +28 -0
- data/lib/well_formed/action_form.rb +7 -0
- data/lib/well_formed/attribute_assignment.rb +42 -0
- data/lib/well_formed/collections.rb +114 -0
- data/lib/well_formed/errors.rb +16 -0
- data/lib/well_formed/initializer.rb +31 -0
- data/lib/well_formed/nested_attributes.rb +194 -0
- data/lib/well_formed/nested_form.rb +14 -0
- data/lib/well_formed/performer.rb +57 -0
- data/lib/well_formed/persistence.rb +61 -0
- data/lib/well_formed/railtie.rb +9 -0
- data/lib/well_formed/record_identity.rb +32 -0
- data/lib/well_formed/resource_form.rb +7 -0
- data/lib/well_formed/simple_action.rb +14 -0
- data/lib/well_formed/simple_nested_form.rb +12 -0
- data/lib/well_formed/simple_resource.rb +14 -0
- data/lib/well_formed/simple_struct.rb +29 -0
- data/lib/well_formed/struct.rb +7 -0
- data/lib/well_formed/transactional.rb +38 -0
- data/lib/well_formed/translations.rb +30 -0
- data/lib/well_formed/version.rb +5 -0
- data/lib/well_formed/with_user.rb +43 -0
- data/lib/well_formed.rb +38 -0
- data/rbs_collection.yaml +19 -0
- data/sig/well_formed.rbs +129 -0
- metadata +105 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module WellFormed
|
|
4
|
+
module Initializer
|
|
5
|
+
def self.included(base)
|
|
6
|
+
raise ArgumentError, "#{name} must be prepended, not included. Use `prepend #{name}` instead."
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
attr_reader :resource
|
|
10
|
+
|
|
11
|
+
def initialize(resource, params = {})
|
|
12
|
+
@resource = resource
|
|
13
|
+
super(resource_defaults.merge(params))
|
|
14
|
+
after_initialize if respond_to?(:after_initialize, true)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
delegate :id, :persisted?, :to_param, to: :resource, allow_nil: true
|
|
18
|
+
|
|
19
|
+
def new_record? = !persisted?
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def resource_defaults
|
|
24
|
+
return {} unless resource
|
|
25
|
+
|
|
26
|
+
self.class.attribute_names.each_with_object({}) do |name, hash|
|
|
27
|
+
hash[name] = resource.public_send(name) if resource.respond_to?(name)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module WellFormed
|
|
4
|
+
module NestedAttributes
|
|
5
|
+
def self.included(base)
|
|
6
|
+
base.extend(ClassMethods)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
module ClassMethods
|
|
10
|
+
# Defines a collection nested form for validating nested attributes on a has_many association.
|
|
11
|
+
#
|
|
12
|
+
# Both `{name}_attributes=` (Rails form_with / form_for) and `{name}=` (API, no suffix)
|
|
13
|
+
# setters are defined automatically — whichever key your params contain will work.
|
|
14
|
+
#
|
|
15
|
+
# @param name [Symbol] the association name (e.g. :line_items)
|
|
16
|
+
# @param form_class [Class, nil] a ResourceForm class. Omit when supplying a block.
|
|
17
|
+
# @yieldparam block inline class body evaluated on an anonymous WellFormed::Base subclass.
|
|
18
|
+
#
|
|
19
|
+
# Records with `_destroy: true` are excluded from validation but forwarded to the resource.
|
|
20
|
+
#
|
|
21
|
+
# Example — inline block:
|
|
22
|
+
# class CreateOrderForm < WellFormed::Base
|
|
23
|
+
# include WellFormed::NestedAttributes
|
|
24
|
+
# nested_attributes_for :line_items do
|
|
25
|
+
# attribute :name, :string
|
|
26
|
+
# validates :name, presence: true
|
|
27
|
+
# end
|
|
28
|
+
# end
|
|
29
|
+
def nested_attributes_for(name, form_class = nil, &block)
|
|
30
|
+
_register_nested(name, form_class, collection: true, &block)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Defines a singular nested form for validating nested attributes on a has_one/belongs_to.
|
|
34
|
+
#
|
|
35
|
+
# Both `{name}_attributes=` and `{name}=` setters are defined automatically.
|
|
36
|
+
#
|
|
37
|
+
# Example — inline block:
|
|
38
|
+
# class CreateOrderForm < WellFormed::Base
|
|
39
|
+
# include WellFormed::NestedAttributes
|
|
40
|
+
# nested_attribute_for :billing_address do
|
|
41
|
+
# attribute :street, :string
|
|
42
|
+
# validates :street, presence: true
|
|
43
|
+
# end
|
|
44
|
+
# end
|
|
45
|
+
def nested_attribute_for(name, form_class = nil, &block)
|
|
46
|
+
_register_nested(name, form_class, collection: false, &block)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def registered_nested_attributes
|
|
50
|
+
inherited = superclass.respond_to?(:registered_nested_attributes) ? superclass.registered_nested_attributes : {}
|
|
51
|
+
inherited.merge(@registered_nested_attributes ||= {})
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
private
|
|
55
|
+
|
|
56
|
+
def _nested_form_builder(synthetic_name, &block)
|
|
57
|
+
form_class = Class.new(WellFormed::SimpleNestedForm, &block)
|
|
58
|
+
form_class.define_singleton_method(:model_name) do
|
|
59
|
+
ActiveModel::Name.new(self, nil, synthetic_name)
|
|
60
|
+
end
|
|
61
|
+
form_class
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def _register_nested(name, form_class, collection:, &block)
|
|
65
|
+
macro = collection ? "nested_attributes_for" : "nested_attribute_for"
|
|
66
|
+
|
|
67
|
+
if form_class && block
|
|
68
|
+
raise ArgumentError, "Pass either a form_class or a block to #{macro} :#{name}, not both"
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
if block
|
|
72
|
+
synthetic_name = name.to_s.singularize.camelize
|
|
73
|
+
form_class = _nested_form_builder(synthetic_name, &block)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
raise ArgumentError, "A form_class or block is required for #{macro} :#{name}" unless form_class
|
|
77
|
+
|
|
78
|
+
(@registered_nested_attributes ||= {})[name] = {form_class: form_class, collection: collection}
|
|
79
|
+
|
|
80
|
+
define_method(name) do
|
|
81
|
+
if instance_variable_defined?(:"@#{name}")
|
|
82
|
+
instance_variable_get(:"@#{name}")
|
|
83
|
+
elsif resource.respond_to?(name)
|
|
84
|
+
raw = resource.public_send(name)
|
|
85
|
+
if collection
|
|
86
|
+
Array(raw).map { |r| _build_nested_form(form_class, r) }
|
|
87
|
+
else
|
|
88
|
+
raw ? _build_nested_form(form_class, raw) : nil
|
|
89
|
+
end
|
|
90
|
+
else
|
|
91
|
+
collection ? [] : nil
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
define_method(:"#{name}_attributes=") do |raw_attributes|
|
|
96
|
+
_build_nested_attributes(name, raw_attributes)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
define_method(:"#{name}=") do |raw_attributes|
|
|
100
|
+
_build_nested_attributes(name, raw_attributes)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
validate :"_validate_nested_#{name}"
|
|
104
|
+
|
|
105
|
+
define_method(:"_validate_nested_#{name}") do
|
|
106
|
+
instances = instance_variable_get(:"@#{name}")
|
|
107
|
+
return unless instances
|
|
108
|
+
|
|
109
|
+
config = self.class.registered_nested_attributes[name]
|
|
110
|
+
|
|
111
|
+
if config[:collection]
|
|
112
|
+
Array(instances).each_with_index do |nested_form, index|
|
|
113
|
+
next if nested_form.valid?
|
|
114
|
+
nested_form.errors.each do |error|
|
|
115
|
+
attr = :"#{name}[#{index}].#{error.attribute}"
|
|
116
|
+
if error.type.is_a?(Symbol)
|
|
117
|
+
errors.add(attr, error.type, **error.options.merge(message: error.message))
|
|
118
|
+
else
|
|
119
|
+
errors.add(attr, error.message)
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
else
|
|
124
|
+
return if instances.valid?
|
|
125
|
+
instances.errors.each do |error|
|
|
126
|
+
attr = :"#{name}.#{error.attribute}"
|
|
127
|
+
if error.type.is_a?(Symbol)
|
|
128
|
+
errors.add(attr, error.type, **error.options.merge(message: error.message))
|
|
129
|
+
else
|
|
130
|
+
errors.add(attr, error.message)
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
private :"_validate_nested_#{name}"
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# ActiveModel::Error#generate_message always calls read_attribute_for_validation
|
|
140
|
+
# to supply a :value interpolation, even when a custom :message is present.
|
|
141
|
+
# Compound nested attribute names (e.g. :"line_items[0].name") are not real
|
|
142
|
+
# methods on the form — return nil for any attribute scoped under a registered
|
|
143
|
+
# nested association to avoid a NoMethodError.
|
|
144
|
+
def read_attribute_for_validation(attr)
|
|
145
|
+
attr_str = attr.to_s
|
|
146
|
+
nested_names = self.class.registered_nested_attributes.keys.map(&:to_s)
|
|
147
|
+
return nil if nested_names.any? { |name| attr_str.start_with?("#{name}[", "#{name}.") }
|
|
148
|
+
super
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
private
|
|
152
|
+
|
|
153
|
+
def _build_nested_attributes(name, raw_attributes)
|
|
154
|
+
@_nested_attributes_raw ||= {}
|
|
155
|
+
@_nested_attributes_raw[:"#{name}_attributes"] = raw_attributes
|
|
156
|
+
|
|
157
|
+
resource.public_send(:"#{name}_attributes=", raw_attributes) if resource.respond_to?(:"#{name}_attributes=")
|
|
158
|
+
|
|
159
|
+
config = self.class.registered_nested_attributes[name]
|
|
160
|
+
form_class = config[:form_class]
|
|
161
|
+
boolean_type = ActiveModel::Type::Boolean.new
|
|
162
|
+
|
|
163
|
+
if config[:collection]
|
|
164
|
+
attrs_list = raw_attributes.is_a?(Array) ? raw_attributes : Array(raw_attributes).map { |_, v| v }
|
|
165
|
+
instances = attrs_list.filter_map do |item_attrs|
|
|
166
|
+
item_attrs = item_attrs.to_h.with_indifferent_access
|
|
167
|
+
next if boolean_type.cast(item_attrs["_destroy"])
|
|
168
|
+
|
|
169
|
+
sub_resource = _find_collection_resource(name, item_attrs)
|
|
170
|
+
_build_nested_form(form_class, sub_resource, item_attrs.except("id", "_destroy"))
|
|
171
|
+
end
|
|
172
|
+
instance_variable_set(:"@#{name}", instances)
|
|
173
|
+
else
|
|
174
|
+
item_attrs = raw_attributes.to_h.with_indifferent_access
|
|
175
|
+
sub_resource = resource.respond_to?(name) ? resource.public_send(name) : nil
|
|
176
|
+
instance_variable_set(:"@#{name}", _build_nested_form(form_class, sub_resource, item_attrs))
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def _build_nested_form(form_class, resource, attrs = {})
|
|
181
|
+
form_class.new(resource, attrs)
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def _find_collection_resource(name, item_attrs)
|
|
185
|
+
id = item_attrs["id"]
|
|
186
|
+
return nil unless id && resource.respond_to?(name)
|
|
187
|
+
|
|
188
|
+
id_str = id.to_s
|
|
189
|
+
resource.public_send(name).find { |r| r.respond_to?(:id) && r.id.to_s == id_str }
|
|
190
|
+
rescue
|
|
191
|
+
nil
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module WellFormed
|
|
4
|
+
# Minimal base class for anonymous nested-attribute forms built via
|
|
5
|
+
# +nested_attributes_for+ / +nested_attribute_for+ blocks.
|
|
6
|
+
#
|
|
7
|
+
# Provides attribute definitions, validations, and collection helpers
|
|
8
|
+
# without the persistence / transactional concerns of ResourceForm.
|
|
9
|
+
# @api private
|
|
10
|
+
class NestedForm < SimpleNestedForm
|
|
11
|
+
include NestedAttributes
|
|
12
|
+
prepend WithUser
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module WellFormed
|
|
4
|
+
module Performer
|
|
5
|
+
module Abstract
|
|
6
|
+
private
|
|
7
|
+
|
|
8
|
+
def perform
|
|
9
|
+
super
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def self.included(base)
|
|
14
|
+
base.include(ActiveSupport::Callbacks)
|
|
15
|
+
base.define_callbacks(:perform)
|
|
16
|
+
base.extend(ClassMethods)
|
|
17
|
+
base.prepend(Abstract)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
module ClassMethods
|
|
21
|
+
def before_perform(*args, &block)
|
|
22
|
+
set_callback(:perform, :before, *args, &block)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def after_perform(*args, &block)
|
|
26
|
+
set_callback(:perform, :after, *args, &block)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def inherited(subclass)
|
|
30
|
+
super
|
|
31
|
+
subclass.prepend(Abstract)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def submit!
|
|
36
|
+
submit || raise(WellFormed::RecordInvalid.new(self))
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def submit
|
|
40
|
+
return false unless valid?
|
|
41
|
+
|
|
42
|
+
performed = false
|
|
43
|
+
run_callbacks(:perform) do
|
|
44
|
+
perform
|
|
45
|
+
performed = true
|
|
46
|
+
end
|
|
47
|
+
errors.add(:base, :could_not_be_performed, message: "could not be performed") if !performed && errors.empty?
|
|
48
|
+
performed
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
private
|
|
52
|
+
|
|
53
|
+
def perform
|
|
54
|
+
raise NotImplementedError, "#{self.class} must implement #perform"
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module WellFormed
|
|
4
|
+
module Persistence
|
|
5
|
+
def self.included(base)
|
|
6
|
+
base.include(ActiveSupport::Callbacks)
|
|
7
|
+
base.include(AttributeAssignment)
|
|
8
|
+
base.define_callbacks(:save)
|
|
9
|
+
base.extend(ClassMethods)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
module ClassMethods
|
|
13
|
+
def before_save(*args, &block)
|
|
14
|
+
set_callback(:save, :before, *args, &block)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def after_save(*args, &block)
|
|
18
|
+
set_callback(:save, :after, *args, &block)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def merge_model_errors
|
|
22
|
+
@merge_model_errors = true
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def merge_model_errors?
|
|
26
|
+
@merge_model_errors || false
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def save
|
|
31
|
+
return false unless valid?
|
|
32
|
+
|
|
33
|
+
result = nil
|
|
34
|
+
catch(:abort) do
|
|
35
|
+
assign_attributes_to(resource)
|
|
36
|
+
run_callbacks(:save) do
|
|
37
|
+
result = perform
|
|
38
|
+
throw(:abort) unless result
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
errors.add(:base, :could_not_be_saved, message: "could not be saved") if result == false && errors.empty?
|
|
42
|
+
result || false
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def save!
|
|
46
|
+
save || raise(WellFormed::RecordInvalid.new(self))
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def submit
|
|
50
|
+
save && resource
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
private
|
|
54
|
+
|
|
55
|
+
def perform
|
|
56
|
+
result = resource.save
|
|
57
|
+
errors.merge!(resource.errors) if !result && self.class.merge_model_errors?
|
|
58
|
+
result
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module WellFormed
|
|
4
|
+
module RecordIdentity
|
|
5
|
+
module CreateBehavior
|
|
6
|
+
def persisted? = false
|
|
7
|
+
def id = nil
|
|
8
|
+
def to_param = nil
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
module UpdateBehavior
|
|
12
|
+
def persisted? = true
|
|
13
|
+
def id = resource.respond_to?(:id) ? resource.id : nil
|
|
14
|
+
delegate :to_param, to: :resource
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
module ClassMethods
|
|
18
|
+
def create_action
|
|
19
|
+
prepend CreateBehavior
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def update_action
|
|
23
|
+
prepend UpdateBehavior
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def self.included(base)
|
|
28
|
+
base.extend(ClassMethods)
|
|
29
|
+
base.create_action
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module WellFormed
|
|
4
|
+
class SimpleAction
|
|
5
|
+
include ActiveModel::Model
|
|
6
|
+
include ActiveModel::Attributes
|
|
7
|
+
include Translations
|
|
8
|
+
include AttributeAssignment
|
|
9
|
+
include Performer
|
|
10
|
+
include NestedAttributes
|
|
11
|
+
prepend Initializer
|
|
12
|
+
include RecordIdentity
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module WellFormed
|
|
4
|
+
class SimpleResource
|
|
5
|
+
include ActiveModel::Model
|
|
6
|
+
include ActiveModel::Attributes
|
|
7
|
+
include Translations
|
|
8
|
+
include Persistence
|
|
9
|
+
include Transactional
|
|
10
|
+
include Collections
|
|
11
|
+
include NestedAttributes
|
|
12
|
+
prepend Initializer
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module WellFormed
|
|
4
|
+
class SimpleStruct
|
|
5
|
+
module PoroInterface
|
|
6
|
+
def id
|
|
7
|
+
resource.respond_to?(:id) ? resource.id : nil
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def persisted?
|
|
11
|
+
resource.respond_to?(:persisted?) ? resource.persisted? : false
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
include ActiveModel::Model
|
|
16
|
+
include ActiveModel::Attributes
|
|
17
|
+
include Translations
|
|
18
|
+
include Persistence
|
|
19
|
+
include NestedAttributes
|
|
20
|
+
prepend Initializer
|
|
21
|
+
prepend PoroInterface
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def perform
|
|
26
|
+
raise NotImplementedError, "#{self.class} must implement #perform"
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_record"
|
|
4
|
+
|
|
5
|
+
module WellFormed
|
|
6
|
+
module Transactional
|
|
7
|
+
def self.included(base)
|
|
8
|
+
base.define_callbacks(:save_commit)
|
|
9
|
+
base.extend(ClassMethods)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
module ClassMethods
|
|
13
|
+
def after_save_commit(*args, &block)
|
|
14
|
+
set_callback(:save_commit, :after, *args, &block)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def save_within_transaction
|
|
18
|
+
set_callback(:save, :around) do |form, block|
|
|
19
|
+
saved = false
|
|
20
|
+
form.resource.class.transaction do
|
|
21
|
+
catch(:abort) do
|
|
22
|
+
block.call
|
|
23
|
+
saved = true
|
|
24
|
+
end
|
|
25
|
+
raise ActiveRecord::Rollback unless saved
|
|
26
|
+
end
|
|
27
|
+
throw(:abort) unless saved
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def save
|
|
33
|
+
result = super
|
|
34
|
+
::ActiveRecord.after_all_transactions_commit { run_callbacks(:save_commit) } if result
|
|
35
|
+
result || false
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module WellFormed
|
|
4
|
+
module Translations
|
|
5
|
+
def self.included(base)
|
|
6
|
+
base.extend(ClassMethods)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
module ClassMethods
|
|
10
|
+
def resource_alias(resource)
|
|
11
|
+
resolved = case resource
|
|
12
|
+
when Class
|
|
13
|
+
raise ArgumentError, "#{resource} does not respond to model_name" unless resource.respond_to?(:model_name)
|
|
14
|
+
|
|
15
|
+
resource.model_name
|
|
16
|
+
when Symbol, String
|
|
17
|
+
name_str = ActiveSupport::Inflector.camelize(resource.to_s)
|
|
18
|
+
ActiveModel::Name.new(Class.new, nil, name_str)
|
|
19
|
+
else
|
|
20
|
+
raise ArgumentError, "resource_alias expects a Class, Symbol, or String, got #{resource.class}"
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
define_singleton_method(:model_name) { resolved }
|
|
24
|
+
|
|
25
|
+
alias_name = resolved.element.to_sym
|
|
26
|
+
alias_method alias_name, :resource
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module WellFormed
|
|
4
|
+
module WithUser
|
|
5
|
+
@extensions = []
|
|
6
|
+
|
|
7
|
+
def self.register_extension(mod)
|
|
8
|
+
@extensions << mod
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def self.prepended(base)
|
|
12
|
+
base.extend(ClassMethods)
|
|
13
|
+
@extensions.each { |mod| base.include(mod) }
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def self.included(base)
|
|
17
|
+
raise ArgumentError, "#{name} must be prepended, not included. Use `prepend #{name}` instead."
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
module ClassMethods
|
|
21
|
+
# Builds an anonymous nested form class rooted at NestedForm.
|
|
22
|
+
def _nested_form_builder(synthetic_name, &block)
|
|
23
|
+
form_class = Class.new(WellFormed::NestedForm, &block)
|
|
24
|
+
form_class.define_singleton_method(:model_name) do
|
|
25
|
+
ActiveModel::Name.new(self, nil, synthetic_name)
|
|
26
|
+
end
|
|
27
|
+
form_class
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
attr_reader :user
|
|
32
|
+
|
|
33
|
+
# Instantiates a nested form with the current user.
|
|
34
|
+
def _build_nested_form(form_class, resource, attrs = {})
|
|
35
|
+
form_class.new(resource, user, attrs)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def initialize(resource, user, params = {})
|
|
39
|
+
@user = user
|
|
40
|
+
super(resource, params)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
data/lib/well_formed.rb
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_model"
|
|
4
|
+
require_relative "well_formed/version"
|
|
5
|
+
require_relative "well_formed/errors"
|
|
6
|
+
require_relative "well_formed/railtie" if defined?(Rails::Railtie)
|
|
7
|
+
|
|
8
|
+
module WellFormed
|
|
9
|
+
autoload :Initializer, "well_formed/initializer"
|
|
10
|
+
autoload :WithUser, "well_formed/with_user"
|
|
11
|
+
autoload :AttributeAssignment, "well_formed/attribute_assignment"
|
|
12
|
+
autoload :Persistence, "well_formed/persistence"
|
|
13
|
+
autoload :Transactional, "well_formed/transactional"
|
|
14
|
+
autoload :Performer, "well_formed/performer"
|
|
15
|
+
autoload :RecordIdentity, "well_formed/record_identity"
|
|
16
|
+
autoload :Translations, "well_formed/translations"
|
|
17
|
+
autoload :NestedAttributes, "well_formed/nested_attributes"
|
|
18
|
+
autoload :Collections, "well_formed/collections"
|
|
19
|
+
autoload :SimpleNestedForm, "well_formed/simple_nested_form"
|
|
20
|
+
autoload :NestedForm, "well_formed/nested_form"
|
|
21
|
+
autoload :SimpleResource, "well_formed/simple_resource"
|
|
22
|
+
autoload :SimpleAction, "well_formed/simple_action"
|
|
23
|
+
autoload :SimpleStruct, "well_formed/simple_struct"
|
|
24
|
+
autoload :ResourceForm, "well_formed/resource_form"
|
|
25
|
+
autoload :ActionForm, "well_formed/action_form"
|
|
26
|
+
autoload :Struct, "well_formed/struct"
|
|
27
|
+
|
|
28
|
+
def self.included(base)
|
|
29
|
+
base.include ActiveModel::Model
|
|
30
|
+
base.include ActiveModel::Attributes
|
|
31
|
+
base.include Translations
|
|
32
|
+
base.include Persistence
|
|
33
|
+
base.include Transactional
|
|
34
|
+
base.include Collections
|
|
35
|
+
base.include NestedAttributes
|
|
36
|
+
base.prepend Initializer
|
|
37
|
+
end
|
|
38
|
+
end
|
data/rbs_collection.yaml
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Download sources
|
|
2
|
+
sources:
|
|
3
|
+
- type: git
|
|
4
|
+
name: ruby/gem_rbs_collection
|
|
5
|
+
remote: https://github.com/ruby/gem_rbs_collection.git
|
|
6
|
+
revision: main
|
|
7
|
+
repo_dir: gems
|
|
8
|
+
|
|
9
|
+
# You can specify local directories as sources also.
|
|
10
|
+
# - type: local
|
|
11
|
+
# path: path/to/your/local/repository
|
|
12
|
+
|
|
13
|
+
# A directory to install the downloaded RBSs
|
|
14
|
+
path: .gem_rbs_collection
|
|
15
|
+
|
|
16
|
+
# gems:
|
|
17
|
+
# # If you want to avoid installing rbs files for gems, you can specify them here.
|
|
18
|
+
# - name: GEM_NAME
|
|
19
|
+
# ignore: true
|