nueca_rails_interfaces 1.1.2 → 1.2.8
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 +4 -4
- data/.rubocop.yml +4 -12
- data/Rakefile +1 -1
- data/lib/nueca_rails_interfaces/v1/query_interface.rb +1 -1
- data/lib/nueca_rails_interfaces/v3/form_interface.rb +166 -0
- data/lib/nueca_rails_interfaces/version.rb +1 -1
- data/lib/nueca_rails_interfaces.rb +1 -1
- data/nueca_rails_interfaces.gemspec +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4666de0cb174c7bfac30b5efbade4c91b77e974747e15ff18e5b7159f3e557fc
|
|
4
|
+
data.tar.gz: 4b487712a93e076adf1431c5fcaa5ed535cdb189b1c87917779b0b8c668d83e3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d5b80763ac2da7bf9720b564716d5f8b1e0bb578f43cad3e7b2ecd8b526393a72a0d682cb4eb2b32936f8c3503e8f76f226e87695db467b3516689c6518bfc47
|
|
7
|
+
data.tar.gz: 9468e0254e8ad17fe0c90e88a477f739718ca32263179a9bac51ab7f8bcafd82231a0c1bfb242777ef76da13ffde2e06a7a49d44f1b5e356bc394c8d2c162328
|
data/.rubocop.yml
CHANGED
|
@@ -1,17 +1,9 @@
|
|
|
1
1
|
plugins:
|
|
2
|
-
- rubocop-
|
|
2
|
+
- rubocop-nueca
|
|
3
3
|
- rubocop-rake
|
|
4
|
-
- rubocop-rspec
|
|
5
4
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
NewCops: enable
|
|
9
|
-
|
|
10
|
-
Layout/LineLength:
|
|
11
|
-
Max: 120
|
|
12
|
-
|
|
13
|
-
RSpec/NestedGroups:
|
|
14
|
-
Max: 5
|
|
5
|
+
Style/ClassMethodsDefinitions:
|
|
6
|
+
Enabled: false
|
|
15
7
|
|
|
16
|
-
|
|
8
|
+
Style/OptionHash:
|
|
17
9
|
Enabled: false
|
data/Rakefile
CHANGED
|
@@ -86,7 +86,7 @@ module NuecaRailsInterfaces
|
|
|
86
86
|
|
|
87
87
|
# Paginates the collection based on query or settings.
|
|
88
88
|
def apply_pagination!
|
|
89
|
-
raise 'Invalid pagination settings.' unless correct_pagination_settings?
|
|
89
|
+
raise 'Invalid pagination settings.' unless correct_pagination_settings? # rubocop:disable Style/ImplicitRuntimeError
|
|
90
90
|
return unless @pagination_flag
|
|
91
91
|
|
|
92
92
|
@collection = collection.paginate(page: fetch_page_value, per_page: fetch_per_page_value)
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module NuecaRailsInterfaces
|
|
4
|
+
module V3
|
|
5
|
+
# V3 Form Interface delegates model validation to the actual model instances
|
|
6
|
+
# rather than reimplementing model validations inside the form.
|
|
7
|
+
#
|
|
8
|
+
# Models managed by the form are declared explicitly with the `model` macro.
|
|
9
|
+
# Their attributes arrive as a nested hash keyed by the model name, which is
|
|
10
|
+
# what `fields_for :school_year, f.object.school_year` produces in the view —
|
|
11
|
+
# e.g. `school_year: { name: "..." }`. This maps directly to:
|
|
12
|
+
# `params.expect(my_form: [{ school_year: [:name] }, :other_field])`
|
|
13
|
+
#
|
|
14
|
+
# Only models listed in the `model` macro are processed; any key whose name
|
|
15
|
+
# matches a declared model and whose value is a Hash (or Parameters) is treated
|
|
16
|
+
# as model attributes. Everything else is treated as a regular form attribute.
|
|
17
|
+
# Fields inside a model hash that the model does not recognise are silently
|
|
18
|
+
# discarded.
|
|
19
|
+
#
|
|
20
|
+
# Custom form-level fields (fields that belong to the form, not to any model)
|
|
21
|
+
# are declared as plain `attr_accessor`s on the form class, just like any other
|
|
22
|
+
# ActiveModel attribute.
|
|
23
|
+
#
|
|
24
|
+
# Calling `valid?` on the form runs form-level validations first, then calls
|
|
25
|
+
# `valid?` on each model instance and propagates any model errors onto the form
|
|
26
|
+
# under the model name key (e.g. `errors[:payment]`). `fields_for :payment`
|
|
27
|
+
# in the view binds to `form.payment` and reads that object's own errors for
|
|
28
|
+
# per-field error rendering, so both levels work naturally together.
|
|
29
|
+
#
|
|
30
|
+
# The `attributes` method returns a hash of model instances merged with any
|
|
31
|
+
# regular form attributes. It can be overridden in the form class.
|
|
32
|
+
module FormInterface
|
|
33
|
+
# Prepended so these methods precede ActiveModel::Model's versions in the
|
|
34
|
+
# method resolution order. When `base.include(ActiveModel::Model)` is called
|
|
35
|
+
# inside the `included` hook, ActiveModel ends up earlier in the ancestor chain,
|
|
36
|
+
# which would otherwise cause ActiveModel::API#initialize to intercept `new`
|
|
37
|
+
# before our param-splitting logic runs.
|
|
38
|
+
module InstanceMethods
|
|
39
|
+
def initialize(options = {})
|
|
40
|
+
@model_instances = {}
|
|
41
|
+
@regular_attributes = extract_regular_attributes(options)
|
|
42
|
+
|
|
43
|
+
# Pre-instantiate declared models that were not present in the params
|
|
44
|
+
# (e.g. the form is used in a `new` action with no input yet).
|
|
45
|
+
(self.class.declared_models - @model_instances.keys).each do |model_sym|
|
|
46
|
+
@model_instances[model_sym] = model_sym.to_s.camelize.constantize.new
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
super(**@regular_attributes)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def valid?(context = nil)
|
|
53
|
+
# Clears errors and runs form-level validations.
|
|
54
|
+
super
|
|
55
|
+
|
|
56
|
+
error_key = self.class.flatten_errors? ? :base : nil
|
|
57
|
+
|
|
58
|
+
@model_instances.each do |model_name, model_instance|
|
|
59
|
+
next if model_instance.valid?
|
|
60
|
+
|
|
61
|
+
model_instance.errors.each do |error|
|
|
62
|
+
errors.add(error_key || model_name, error.full_message)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
errors.empty?
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def method_missing(name, *args, &)
|
|
70
|
+
return @model_instances[name] if @model_instances.key?(name)
|
|
71
|
+
|
|
72
|
+
super
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def respond_to_missing?(name, include_private = false)
|
|
76
|
+
@model_instances.key?(name) || super
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
private
|
|
80
|
+
|
|
81
|
+
# Builds the model instance from the given attributes hash, assigning only
|
|
82
|
+
# fields the model recognises. Unknown fields are silently discarded.
|
|
83
|
+
# `attrs` may be a plain Hash or ActionController::Parameters — both are
|
|
84
|
+
# normalised to a plain Hash before processing; field whitelisting via
|
|
85
|
+
# Splits options into model instances and regular form attributes.
|
|
86
|
+
# Hash-valued keys matching a declared model are built into model instances.
|
|
87
|
+
# Hash-valued keys for undeclared models are silently discarded.
|
|
88
|
+
# All other (scalar) keys are returned as regular attributes for super.
|
|
89
|
+
def extract_regular_attributes(options)
|
|
90
|
+
regular_attrs = {}
|
|
91
|
+
options.each do |key, value|
|
|
92
|
+
if self.class.declared_models.include?(key.to_sym) && value.respond_to?(:each_pair)
|
|
93
|
+
build_model_instance(key.to_sym, value)
|
|
94
|
+
elsif !value.respond_to?(:each_pair)
|
|
95
|
+
regular_attrs[key] = value
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
regular_attrs
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# `model_field?` provides the equivalent of strong-param filtering here.
|
|
102
|
+
def build_model_instance(model_name, attrs)
|
|
103
|
+
model_class = model_name.to_s.camelize.constantize
|
|
104
|
+
attrs_hash = attrs.respond_to?(:to_unsafe_h) ? attrs.to_unsafe_h : attrs.to_h
|
|
105
|
+
model_attrs = attrs_hash.each_with_object({}) do |(field, value), hash|
|
|
106
|
+
hash[field.to_sym] = value if model_field?(model_class, field.to_s)
|
|
107
|
+
end
|
|
108
|
+
@model_instances[model_name] = model_class.new(**model_attrs)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# A field belongs to the model if the model class declares it via
|
|
112
|
+
# `attribute_names` (ActiveRecord) or has an assignment method
|
|
113
|
+
# (ActiveModel attr_accessor, etc.).
|
|
114
|
+
def model_field?(model_class, field_name)
|
|
115
|
+
(model_class.respond_to?(:attribute_names) && model_class.attribute_names.include?(field_name)) ||
|
|
116
|
+
model_class.method_defined?("#{field_name}=")
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
class << self
|
|
121
|
+
def included(base)
|
|
122
|
+
base.include(ActiveModel::Model)
|
|
123
|
+
base.prepend(InstanceMethods)
|
|
124
|
+
define_class_macros(base)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
private
|
|
128
|
+
|
|
129
|
+
def define_class_macros(base)
|
|
130
|
+
base.instance_variable_set(:@declared_models, [])
|
|
131
|
+
base.instance_variable_set(:@flatten_errors, false)
|
|
132
|
+
|
|
133
|
+
base.define_singleton_method(:flatten_errors) { @flatten_errors = true }
|
|
134
|
+
base.define_singleton_method(:flatten_errors?) { @flatten_errors }
|
|
135
|
+
|
|
136
|
+
# Declares one or more models managed by this form. Each symbol must be
|
|
137
|
+
# the underscored model name (e.g. `model :payment`, `model :school_year`).
|
|
138
|
+
# Only declared models are processed from params. Declared models are always instantiated
|
|
139
|
+
# (with blank attributes) even when their params are absent.
|
|
140
|
+
base.define_singleton_method(:model) do |*model_names|
|
|
141
|
+
model_names.each { |n| @declared_models << n.to_sym }
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
base.define_singleton_method(:declared_models) { @declared_models }
|
|
145
|
+
|
|
146
|
+
base.define_singleton_method(:check) do |*arguments|
|
|
147
|
+
instance = NuecaRailsInterfaces::Util.process_class_arguments(self, *arguments)
|
|
148
|
+
instance.valid?
|
|
149
|
+
instance
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# Returns a merged hash of model instances and regular form attributes.
|
|
155
|
+
# Defined here (included, not prepended) so that a form class can override it
|
|
156
|
+
# by defining its own `attributes` method — the class-level definition is found
|
|
157
|
+
# first in the MRO, falling back to this default when no override exists.
|
|
158
|
+
def attributes
|
|
159
|
+
result = {}
|
|
160
|
+
result.merge!(@model_instances)
|
|
161
|
+
result.merge!(@regular_attributes.transform_keys(&:to_sym))
|
|
162
|
+
result.with_indifferent_access
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
end
|
|
@@ -16,7 +16,7 @@ Gem::Specification.new do |spec|
|
|
|
16
16
|
spec.files = Dir.chdir(__dir__) do
|
|
17
17
|
`git ls-files -z`.split("\x0").reject do |f|
|
|
18
18
|
(File.expand_path(f) == __FILE__) ||
|
|
19
|
-
f.start_with?(
|
|
19
|
+
f.start_with?('bin/', 'test/', 'spec/', 'features/', '.git', '.gitlab-ci.yml', 'appveyor', 'Gemfile')
|
|
20
20
|
end
|
|
21
21
|
end
|
|
22
22
|
spec.bindir = 'exe'
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: nueca_rails_interfaces
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.2.8
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Tien
|
|
@@ -64,6 +64,7 @@ files:
|
|
|
64
64
|
- lib/nueca_rails_interfaces/v1/service_interface.rb
|
|
65
65
|
- lib/nueca_rails_interfaces/v2/form_interface.rb
|
|
66
66
|
- lib/nueca_rails_interfaces/v2/service_interface.rb
|
|
67
|
+
- lib/nueca_rails_interfaces/v3/form_interface.rb
|
|
67
68
|
- lib/nueca_rails_interfaces/v3/service_interface.rb
|
|
68
69
|
- lib/nueca_rails_interfaces/version.rb
|
|
69
70
|
- nueca_rails_interfaces.gemspec
|
|
@@ -86,7 +87,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
86
87
|
- !ruby/object:Gem::Version
|
|
87
88
|
version: '0'
|
|
88
89
|
requirements: []
|
|
89
|
-
rubygems_version:
|
|
90
|
+
rubygems_version: 4.0.7
|
|
90
91
|
specification_version: 4
|
|
91
92
|
summary: Interfaces for known object entities in Rails Development at Nueca.
|
|
92
93
|
test_files: []
|