phlexi-field 0.0.1 → 0.0.3
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/lib/phlexi/field/builder.rb +9 -7
- data/lib/phlexi/field/options/associations.rb +2 -2
- data/lib/phlexi/field/options/attachments.rb +2 -2
- data/lib/phlexi/field/options/inferred_types.rb +40 -36
- data/lib/phlexi/field/options/multiple.rb +66 -0
- data/lib/phlexi/field/options/validators.rb +48 -0
- data/lib/phlexi/field/structure/dom.rb +1 -1
- data/lib/phlexi/field/structure/field_collection.rb +2 -0
- data/lib/phlexi/field/structure/namespace.rb +5 -5
- data/lib/phlexi/field/structure/node.rb +1 -1
- data/lib/phlexi/field/theme.rb +9 -6
- data/lib/phlexi/field/version.rb +1 -1
- data/lib/phlexi/field.rb +10 -2
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 70fdd02c6979235fa18a95692de12b4ebefec2468e2bb71bb0a2a25d7692de78
|
4
|
+
data.tar.gz: 40feabf408438dc28b7907c8e66b5d04aa5d336e986ab5cbfc08c6a51b290f33
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ae593aa3bc1c50f6a4f4ff93caf9553de907ddf53759d282ab2ede1b5cba99b0a001ff8853f01fcf205e8eb0dfe237a0a5c378866dd4b7cc1793a901fefbfd24
|
7
|
+
data.tar.gz: 7abc8faa67e3a6b7a4baf232f507dc0d7923bb3e722852ff9b63e2d8b8771e09de538d5db05eb5a18ec97e16a4776d7b5bd93dafa6d30cef79162e45155761a5
|
data/lib/phlexi/field/builder.rb
CHANGED
@@ -13,13 +13,15 @@ module Phlexi
|
|
13
13
|
# @attr_accessor [Object] value The value of the field.
|
14
14
|
class Builder < Structure::Node
|
15
15
|
include Phlex::Helpers
|
16
|
-
include Options::
|
17
|
-
include Options::Attachments
|
18
|
-
include Options::Descriptions
|
19
|
-
include Options::Hints
|
16
|
+
include Options::Validators
|
20
17
|
include Options::InferredTypes
|
18
|
+
include Options::Multiple
|
21
19
|
include Options::Labels
|
22
20
|
include Options::Placeholders
|
21
|
+
include Options::Descriptions
|
22
|
+
include Options::Hints
|
23
|
+
include Options::Associations
|
24
|
+
include Options::Attachments
|
23
25
|
|
24
26
|
class DOM < Structure::DOM; end
|
25
27
|
|
@@ -52,12 +54,12 @@ module Phlexi
|
|
52
54
|
self.class::FieldCollection.new(field: self, collection:, &)
|
53
55
|
end
|
54
56
|
|
55
|
-
protected
|
56
|
-
|
57
57
|
def has_value?
|
58
|
-
value.present?
|
58
|
+
attachment_reflection.present? ? value.attached? : (value.present? || value == false)
|
59
59
|
end
|
60
60
|
|
61
|
+
protected
|
62
|
+
|
61
63
|
def determine_initial_value(value)
|
62
64
|
return value unless value == NIL_VALUE
|
63
65
|
|
@@ -4,12 +4,12 @@ module Phlexi
|
|
4
4
|
module Field
|
5
5
|
module Options
|
6
6
|
module Associations
|
7
|
-
protected
|
8
|
-
|
9
7
|
def association_reflection
|
10
8
|
@association_reflection ||= find_association_reflection
|
11
9
|
end
|
12
10
|
|
11
|
+
protected
|
12
|
+
|
13
13
|
def find_association_reflection
|
14
14
|
if object.class.respond_to?(:reflect_on_association)
|
15
15
|
object.class.reflect_on_association(key)
|
@@ -4,12 +4,12 @@ module Phlexi
|
|
4
4
|
module Field
|
5
5
|
module Options
|
6
6
|
module Attachments
|
7
|
-
protected
|
8
|
-
|
9
7
|
def attachment_reflection
|
10
8
|
@attachment_reflection ||= find_attachment_reflection
|
11
9
|
end
|
12
10
|
|
11
|
+
protected
|
12
|
+
|
13
13
|
def find_attachment_reflection
|
14
14
|
if object.class.respond_to?(:reflect_on_attachment)
|
15
15
|
object.class.reflect_on_attachment(key)
|
@@ -6,44 +6,38 @@ module Phlexi
|
|
6
6
|
module Field
|
7
7
|
module Options
|
8
8
|
module InferredTypes
|
9
|
+
def inferred_field_component
|
10
|
+
@inferred_component ||= infer_field_component
|
11
|
+
end
|
12
|
+
|
9
13
|
def inferred_field_type
|
10
14
|
@inferred_field_type ||= infer_field_type
|
11
15
|
end
|
12
16
|
|
13
|
-
def
|
14
|
-
@
|
17
|
+
def inferred_string_field_type
|
18
|
+
@inferred_string_field_type || infer_string_field_type
|
15
19
|
end
|
16
20
|
|
17
21
|
private
|
18
22
|
|
19
23
|
def infer_field_component
|
20
|
-
|
21
|
-
when :string, :text
|
22
|
-
infer_string_field_component(key)
|
23
|
-
when :integer, :float, :decimal
|
24
|
-
:number
|
25
|
-
when :date, :datetime, :time
|
26
|
-
:date
|
27
|
-
when :boolean
|
28
|
-
:boolean
|
29
|
-
when :json, :jsonb, :hstore
|
30
|
-
:code
|
31
|
-
else
|
32
|
-
if association_reflection
|
33
|
-
:association
|
34
|
-
elsif attachment_reflection
|
35
|
-
:attachment
|
36
|
-
else
|
37
|
-
:text
|
38
|
-
end
|
39
|
-
end
|
24
|
+
inferred_field_type
|
40
25
|
end
|
41
26
|
|
42
27
|
def infer_field_type
|
28
|
+
# Check attachments first since they are implemented as associations
|
29
|
+
return :attachment if attachment_reflection
|
30
|
+
|
31
|
+
return :association if association_reflection
|
32
|
+
|
33
|
+
if object.class.respond_to?(:defined_enums)
|
34
|
+
return :enum if object.class.defined_enums.key?(key.to_s)
|
35
|
+
end
|
36
|
+
|
43
37
|
if object.class.respond_to?(:columns_hash)
|
44
|
-
# ActiveRecord
|
38
|
+
# ActiveRecord
|
45
39
|
column = object.class.columns_hash[key.to_s]
|
46
|
-
return column.type if column
|
40
|
+
return column.type if column&.type
|
47
41
|
end
|
48
42
|
|
49
43
|
if object.class.respond_to?(:attribute_types)
|
@@ -58,6 +52,12 @@ module Phlexi
|
|
58
52
|
return infer_field_type_from_value(object.send(key))
|
59
53
|
end
|
60
54
|
|
55
|
+
# Check if object is a has that contains key
|
56
|
+
if object.respond_to?(:fetch)
|
57
|
+
# Fallback to inferring type from the value
|
58
|
+
return infer_field_type_from_value(object.fetch(key))
|
59
|
+
end
|
60
|
+
|
61
61
|
# Default to string if we can't determine the type
|
62
62
|
:string
|
63
63
|
end
|
@@ -83,18 +83,24 @@ module Phlexi
|
|
83
83
|
end
|
84
84
|
end
|
85
85
|
|
86
|
-
def
|
87
|
-
|
88
|
-
|
89
|
-
return :password if is_password_field?
|
86
|
+
def infer_string_field_type
|
87
|
+
infer_string_field_type_from_key || infer_string_field_type_from_validations
|
88
|
+
end
|
90
89
|
|
91
|
-
|
92
|
-
return
|
90
|
+
def infer_string_field_type_from_validations
|
91
|
+
return unless has_validators?
|
93
92
|
|
94
|
-
:
|
93
|
+
if attribute_validators.find { |v| v.kind == :numericality }
|
94
|
+
:number
|
95
|
+
elsif attribute_validators.find { |v| v.kind == :format && v.options[:with] == URI::MailTo::EMAIL_REGEXP }
|
96
|
+
:email
|
97
|
+
end
|
95
98
|
end
|
96
99
|
|
97
|
-
def
|
100
|
+
def infer_string_field_type_from_key
|
101
|
+
key = self.key.to_s.downcase
|
102
|
+
return :password if is_password_field?(key)
|
103
|
+
|
98
104
|
custom_mappings = {
|
99
105
|
/url$|^link|^site/ => :url,
|
100
106
|
/^email/ => :email,
|
@@ -103,7 +109,7 @@ module Phlexi
|
|
103
109
|
/^time/ => :time,
|
104
110
|
/^date/ => :date,
|
105
111
|
/^number|_count$|_amount$/ => :number,
|
106
|
-
/^color
|
112
|
+
/^color|_color$/ => :color
|
107
113
|
}
|
108
114
|
|
109
115
|
custom_mappings.each do |pattern, type|
|
@@ -113,9 +119,7 @@ module Phlexi
|
|
113
119
|
nil
|
114
120
|
end
|
115
121
|
|
116
|
-
def is_password_field?
|
117
|
-
key = self.key.to_s.downcase
|
118
|
-
|
122
|
+
def is_password_field?(key)
|
119
123
|
exact_matches = ["password"]
|
120
124
|
prefixes = ["encrypted_"]
|
121
125
|
suffixes = ["_password", "_digest", "_hash", "_token"]
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Phlexi
|
4
|
+
module Field
|
5
|
+
module Options
|
6
|
+
module Multiple
|
7
|
+
def multiple?
|
8
|
+
options[:multiple] = options.fetch(:multiple) { calculate_multiple_field_value }
|
9
|
+
end
|
10
|
+
|
11
|
+
def multiple!(multiple = true)
|
12
|
+
options[:multiple] = multiple
|
13
|
+
self
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def calculate_multiple_field_value
|
19
|
+
return true if attachment_reflection&.macro == :has_many_attached
|
20
|
+
return true if %i[has_many has_and_belongs_to_many].include?(association_reflection&.macro)
|
21
|
+
return true if multiple_field_array_attribute?
|
22
|
+
|
23
|
+
check_multiple_field_from_validators
|
24
|
+
end
|
25
|
+
|
26
|
+
def multiple_field_array_attribute?
|
27
|
+
return false unless object.class.respond_to?(:columns_hash)
|
28
|
+
|
29
|
+
column = object.class.columns_hash[key.to_s]
|
30
|
+
return false unless column
|
31
|
+
|
32
|
+
case object.class.connection.adapter_name.downcase
|
33
|
+
when "postgresql"
|
34
|
+
column.array? || (column.type == :string && column.sql_type.include?("[]"))
|
35
|
+
end # || object.class.attribute_types[key.to_s].is_a?(ActiveRecord::Type::Serialized)
|
36
|
+
rescue
|
37
|
+
# Rails.logger.warn("Error checking multiple field array attribute: #{e.message}")
|
38
|
+
false
|
39
|
+
end
|
40
|
+
|
41
|
+
def check_multiple_field_from_validators
|
42
|
+
inclusion_validator = find_validator(:inclusion)
|
43
|
+
length_validator = find_validator(:length)
|
44
|
+
|
45
|
+
return false unless inclusion_validator || length_validator
|
46
|
+
|
47
|
+
check_multiple_field_inclusion_validator(inclusion_validator) ||
|
48
|
+
check_multiple_field_length_validator(length_validator)
|
49
|
+
end
|
50
|
+
|
51
|
+
def check_multiple_field_inclusion_validator(validator)
|
52
|
+
return false unless validator
|
53
|
+
in_option = validator.options[:in] || validator.options[:within]
|
54
|
+
return false unless in_option.is_a?(Array)
|
55
|
+
|
56
|
+
validator.options[:multiple] == true || (multiple_field_array_attribute? && in_option.size > 1)
|
57
|
+
end
|
58
|
+
|
59
|
+
def check_multiple_field_length_validator(validator)
|
60
|
+
return false unless validator
|
61
|
+
validator.options[:maximum].to_i > 1 if validator.options[:maximum]
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Phlexi
|
4
|
+
module Field
|
5
|
+
module Options
|
6
|
+
module Validators
|
7
|
+
private
|
8
|
+
|
9
|
+
def has_validators?
|
10
|
+
@has_validators ||= object.class.respond_to?(:validators_on)
|
11
|
+
end
|
12
|
+
|
13
|
+
def attribute_validators
|
14
|
+
object.class.validators_on(key)
|
15
|
+
end
|
16
|
+
|
17
|
+
def association_reflection_validators
|
18
|
+
association_reflection ? object.class.validators_on(association_reflection.name) : []
|
19
|
+
end
|
20
|
+
|
21
|
+
def valid_validator?(validator)
|
22
|
+
!conditional_validators?(validator) && action_validator_match?(validator)
|
23
|
+
end
|
24
|
+
|
25
|
+
def conditional_validators?(validator)
|
26
|
+
validator.options.include?(:if) || validator.options.include?(:unless)
|
27
|
+
end
|
28
|
+
|
29
|
+
def action_validator_match?(validator)
|
30
|
+
return true unless validator.options.include?(:on)
|
31
|
+
|
32
|
+
case validator.options[:on]
|
33
|
+
when :save
|
34
|
+
true
|
35
|
+
when :create
|
36
|
+
!object.persisted?
|
37
|
+
when :update
|
38
|
+
object.persisted?
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def find_validator(kind)
|
43
|
+
attribute_validators.find { |v| v.kind == kind && valid_validator?(v) } if has_validators?
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -18,6 +18,8 @@ module Phlexi
|
|
18
18
|
class Namespace < Structure::Node
|
19
19
|
include Enumerable
|
20
20
|
|
21
|
+
class NamespaceCollection < Structure::NamespaceCollection; end
|
22
|
+
|
21
23
|
attr_reader :builder_klass, :object
|
22
24
|
|
23
25
|
def initialize(key, parent:, builder_klass:, object: nil)
|
@@ -71,7 +73,7 @@ module Phlexi
|
|
71
73
|
# to another `Namespace` or `Field`.
|
72
74
|
def nest_many(key, collection: nil, &)
|
73
75
|
collection ||= Array(object_value_for(key: key))
|
74
|
-
create_child(key, NamespaceCollection, collection:, &)
|
76
|
+
create_child(key, self.class::NamespaceCollection, collection:, &)
|
75
77
|
end
|
76
78
|
|
77
79
|
# Iterates through the children of the current namespace, which could be `Namespace` or `Field`
|
@@ -84,10 +86,8 @@ module Phlexi
|
|
84
86
|
@dom_id ||= begin
|
85
87
|
id = if object.nil?
|
86
88
|
nil
|
87
|
-
elsif
|
88
|
-
|
89
|
-
elsif object.respond_to?(:id)
|
90
|
-
object.id || :new
|
89
|
+
elsif (primary_key = Phlexi::Field.object_primary_key(object))
|
90
|
+
primary_key&.to_s || :new
|
91
91
|
end
|
92
92
|
[key, id].compact.join("_").underscore
|
93
93
|
end
|
data/lib/phlexi/field/theme.rb
CHANGED
@@ -2,11 +2,10 @@ require "fiber/local"
|
|
2
2
|
|
3
3
|
module Phlexi
|
4
4
|
module Field
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
extend Fiber::Local
|
5
|
+
class Theme
|
6
|
+
def self.inherited(subclass)
|
7
|
+
super
|
8
|
+
subclass.extend Fiber::Local
|
10
9
|
end
|
11
10
|
|
12
11
|
# Retrieves the theme hash
|
@@ -26,8 +25,12 @@ module Phlexi
|
|
26
25
|
#
|
27
26
|
# @example Theme inheritance
|
28
27
|
# theme[:email] # Returns :text, indicating email inherits text's theme
|
28
|
+
def self.theme
|
29
|
+
raise NotImplementedError, "#{self} must implement #self.theme"
|
30
|
+
end
|
31
|
+
|
29
32
|
def theme
|
30
|
-
|
33
|
+
@theme ||= self.class.theme.freeze
|
31
34
|
end
|
32
35
|
|
33
36
|
# Recursively resolves the theme for a given property, handling nested symbol references
|
data/lib/phlexi/field/version.rb
CHANGED
data/lib/phlexi/field.rb
CHANGED
@@ -5,6 +5,8 @@ require "phlex"
|
|
5
5
|
require "active_support/core_ext/object/blank"
|
6
6
|
|
7
7
|
module Phlexi
|
8
|
+
NIL_VALUE = :__i_phlexi_i__
|
9
|
+
|
8
10
|
module Field
|
9
11
|
Loader = Zeitwerk::Loader.new.tap do |loader|
|
10
12
|
loader.tag = File.basename(__FILE__, ".rb")
|
@@ -19,8 +21,14 @@ module Phlexi
|
|
19
21
|
|
20
22
|
COMPONENT_BASE = (defined?(::ApplicationComponent) ? ::ApplicationComponent : Phlex::HTML)
|
21
23
|
|
22
|
-
NIL_VALUE = :__i_phlexi_i__
|
23
|
-
|
24
24
|
class Error < StandardError; end
|
25
|
+
|
26
|
+
def self.object_primary_key(object)
|
27
|
+
if object.class.respond_to?(:primary_key)
|
28
|
+
object.send(object.class.primary_key.to_sym)
|
29
|
+
elsif object.respond_to?(:id)
|
30
|
+
object.id
|
31
|
+
end
|
32
|
+
end
|
25
33
|
end
|
26
34
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: phlexi-field
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Stefan Froelich
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-10-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: phlex
|
@@ -208,7 +208,9 @@ files:
|
|
208
208
|
- lib/phlexi/field/options/hints.rb
|
209
209
|
- lib/phlexi/field/options/inferred_types.rb
|
210
210
|
- lib/phlexi/field/options/labels.rb
|
211
|
+
- lib/phlexi/field/options/multiple.rb
|
211
212
|
- lib/phlexi/field/options/placeholders.rb
|
213
|
+
- lib/phlexi/field/options/validators.rb
|
212
214
|
- lib/phlexi/field/structure/dom.rb
|
213
215
|
- lib/phlexi/field/structure/field_collection.rb
|
214
216
|
- lib/phlexi/field/structure/namespace.rb
|